考虑有一个动态库
头文件myso.h
#ifndef __MYSO_H__
#define __MYSO_H__
class A
{
public:
A(int i);
int iValue();
private:
int _i;
};
#endif
定义文件myso.cpp
#include "myso.h"
A::A(int i):_i(i)
{
}
int A::iValue()
{
return _i;
}
编译生成动态库libmyso.so
g++ -fpic -shared myso.cpp -o libmyso.so -I.
代码main.cpp中调用 libmyso
#include
#include "myso.h"
using namespace std;
int main()
{
A a(10);
cout<<a.iValue()<<endl;
return 0;
}
编译生成可执行文件a.out
g++ -c main.cpp -I.
g++ main.o -L -lmyso
使用脚本a.sh导入库路径
LD_LIBRARY_PATH=/home/wang/program/C++/dll
export LD_LIBRARY_PATH
./a.out
./a.sh 输出10
如果为动态库中增加函数 void setIValue(int i);
头文件myso.h
#ifndef __MYSO_H__
#define __MYSO_H__
class A
{
public:
A(int i);
int iValue();
void setIValue(int i);
private:
int _i;
};
#endif
#include "myso.h"
A::A(int i):_i(i)
{
}
int A::iValue()
{
return _i;
}
void A::setIValue(int i)
{
_i = i;
}
更新动态库
g++ -fpic -shared myso.cpp -o libmyso.so -I.
不编译main.cpp a.sh运行功能正常,也就是说增加普通函数是二进制兼容的。
如果,添加的不是普通函数,而是变量 int _j;
头文件myso.h
#ifndef __MYSO_H__
#define __MYSO_H__
class A
{
public:
A(int i);
int iValue();
private:
int _j;
int _i;
};
#endif
定义文件myso.cpp
#include "myso.h"
A::A(int i):_i(i)
{
}
int A::iValue()
{
return _i;
}
编译生成动态库libmyso.so
g++ -fpic -shared myso.cpp -o libmyso.so -I.
运行./a.sh 输出
报错。原因是main.cpp在编译生成时,根据A的声明为栈上变量a分配空间,这时,A只有一个int变量。所以main函数的栈空间为4字节。
当A增加一个int变量后,a.out在调用链接函数
A a(10)
a.iValue();
相当于调用了
A(A * )
IValue(A *)
这两个函数认为main函数传递给它了一个8字节的栈上空间的首地址,构造函数给后4字节空间赋值10,IValue读取后4字节的int值。操作了未分配的非法空间,所以报错。
当然,如果把int _j放在 变量 _i之下,那么构造函数和IValue只操作了分配的4个字节,a.out不会报错。
但是,C++是面向对象语言,也就是说它会有继承。
如果头文件myso2.h
#ifndef __MYSO2_H__
#define __MYSO2_H__
#include "myso.h"
class B:public A
{
public:
B(int i,int x) ;
int xValue();
private:
int _x;
};
#endif
定义文件myso2.cpp
#include "myso2.h"
B::B(int i,int x):A(i),_x(x)
{
}
int B::xValue()
{
return _x;
}
生成动态库libmyso2.so
g++ -fpic -shared myso2.cpp -o libmyso2.so -I. -L -lmyso -l myso2
main.cpp
#include
#include "myso2.h"
using namespace std;
int main()
{
B b(10,20);
cout<<b.xValue()<<endl;
return 0;
}
编译main.cpp
g++ main.cpp -I. -L. -lmyso -lmyso2
./a.sh 输出20
如果A类中增加一个int _j,更新libmyso.so 和libmyso2.so。可执行文件a.ou不变。
./a.sh 报错,应为main() 函数中只分配了8个字节栈上空间,而在动态库更新后的B构造函数和xValue访问了8字节之后的4个字节。这是未分配的内存。
这就是继承的高耦合性,所以,大多数C++ 规范要求优先考虑使用组合替换继承。组合可以实现依赖隔离,并且封装变化点。所以组合的耦合性更低。组合依靠PIMPL实现高内聚,低耦合。
myso.h
#ifndef __myso_h__
#define __myso_h__
class A{
public:
A(int i);
~A();
A(const A&)=delete;
A& operator=(A&)=delete;
int iValue();
private:
class Aimpl;
Aimpl *pAimpl;
};
#endif
myso.cpp
#include "myso.h"
//内置类型 Aimpl 声明
class A::Aimpl{
public:
Aimpl(int i):_i(i){}
int iValue()
{
return _i;
}
private:
int _i;
};
A::A(int i):pAimpl(new Aimpl(i))
{
}
A::~A()
{
delete pAimpl;
}
//函数转发
int A::iValue()
{
return pAimpl->iValue();
}
g++ -shared -fpIc myso.cpp -o myso.so -I. 编译
我们实际要使用的是Aimp,A只是暴露的接口,同时,Aimpl对象的构造限制在共享库内部,不会有分配资源和使用资源有差异的情况发生,Aimpl的大小不论如何变化,接口A的大小都只是一个指针大小。在64位系统中,就是64位。
同理,B对象可修改为
myso2.h
#ifndef __myso2_h__
#define __myso2_h__
class B{
public:
B(int x,int i);
~B();
B(const B&)=delete;
B& operator=(B&)=delete;
int xValue();
int iValue();
private:
class Bimpl;
Aimpl *pBimpl;
};
#endif
myso2.cpp
#include "myso2.h"
#include "myso.h"
class B::Bimpl{
public:
Bimpl(int x,int i):a(i),_x(x)
{}
int xValue()
{
return _x;
}
int iValue()
{
return a.iValue();
}
private:
int _x;
A a;
};
B::B(int x,int i):pBimpl(new Bimpl(x,i))
{
}
B::~B()
{
delete pBimpl;
}
int B::xValue()
{
return pBimpl->xValue();
}
int B::iValue()
{
return pBimpl->iValue();
}
g++ -shared -fpic myso2.cpp -o libmyso2.so -I. -L. -lmyso
main.c
main.cpp
```c
#include
#include "myso2.h"
using namespace std;
int main()
{
B b(10,20);
cout<<b.xValue()<<endl;
cout<<b.iValue()<<endl;
return 0;
}
g++ main.cpp -I. -L -lmyso -lmyso.2
不论怎么在Aimpl 和Bimpl中添加变量,更新两个动态库后,a.out的功能正常。而且这个源文件编译时依赖的myso2.h中并不依赖myso.h,实现了依赖隔离。而且对于最终用户来说,Bimpl和Aimpl中的属性是不可见的。
继承是为被复用,而非复用,也就是说,继承不应该是复用实现,而是为了复用虚函数接口。
变量是依靠相对位置访问,所以,继承体系下增加变量,会导致其它变量的位置发生变化,变化的恶性扩散是的用户程序功能不正常。偏偏,虚函数就是依靠相对位置访问,同时,他又和继承绑在一起,无法避免。虚函数会破坏二进制兼容。
base.h
#ifndef __BASE_H__
#define __BASE_H__
class Base{
virtual void f();
virtual void g();
//virtual void h();
};
#endif
base.cpp
#include
#include "base.h"
void Base::f()
{
std::cout<<"Base::f call"<<std::endl;
}
void Base::g()
{
std::cout<<"Base::f call"<<std::endl;
}
//void Base::h()
//{
// std::cout<<"Base::h call"<
//}
子类 覆盖一个虚函数 添加一个虚函数
child.h
#ifndef __CHILD_H__
#define __CHILD_H__
#include "base.h"
class Child:public Base
{
virtual void g();
};
#endif
child.cpp
#include
#include "child.h"
void Child::g()
{
std::cout<<"Child::g call"<<std::endl;
}
g++ -fpic -shared child.cpp base.cpp -o libvirtual.so -I.
用户程序main.cpp
#include "child.h"
int main()
{
Child c;
//c 虚表中有两个个函数,B::f C:g
//c.g(); //调用第二个 vptrtable[1]
return 0;
}
编译 g++ main.cpp -I. -L. -lvirtual
运行,没有任何输出,正常退出。如果去掉Base 中的h()函数注释
重新编译virtual共享 库,重新运行可执行文件。有报错输出。
Symbol `_ZTV5Child’ has different size in shared object, consider re-linking
这是因为第一次的child 对象中有两个虚函数,堆上需要两个函数指针的空间,child对象中有一个栈上表指针。child对象的创建过程是,先分配内存(一个栈上指针,两个堆上指针的空间),然后调用构造函数,给虚表赋值。可执行文件编译后,这种资源分配就固定了。当动态库改变后,链接库中的CHILD构造函数认为对象有三个函数指针,它像一个2个指针大小的栈上空间存储了三个函数指针。导致错误。所以,如果需要保证ABI,则不能使用虚函数。这时,如果需要运行时多态。可以考虑
虚函数方式的观察者模式
subjec.h
#ifndef __SUBJECT_H__
#define __SUBJECT_H__
class WeatherData;
class WeatherDataObserver
{
public:
virtual void update(WeatherData * wd)=0;
};
class Subject
{
public:
virtual void registerObserver(WeatherDataObserver * o)=0;
virtual void removeObserver(WeatherDataObserver * o) =0;
virtual void notifyObservers()=0;
};
#endif
weatherData.h
#ifndef __WEATHERDATA_H__
#define __WEATHERDATA_H__
#include
#include "subject.h"
class WeatherData:public Subject
{
public:
float getTemp();
float getPressure();
virtual void registerObserver(WeatherDataObserver *o)override ;
virtual void removeObserver(WeatherDataObserver *o)override ;
virtual void notifyObservers()override ;
void setData(float temp,float pressure);
private:
float _temp;
float _pressure;
std::list<WeatherDataObserver *> _observers;
};
#endif
weatherData.cpp
#include "weatherData.h"
float WeatherData::getTemp()
{
return _temp;
}
float WeatherData::getPressure()
{
return _pressure;
}
void WeatherData::registerObserver(WeatherDataObserver *o)
{
_observers.push_back(o);
}
void WeatherData::removeObserver(WeatherDataObserver *o)
{
_observers.remove(o);
}
void WeatherData::setData(float temp,float pressure)
{
_temp = temp;
_pressure = pressure;
notifyObservers();
}
void WeatherData::notifyObservers()
{
for(WeatherDataObserver *o:_observers)
{
o->update(this);
}
}
weatherDataDisplay.h
#ifndef __WEATHDATADISPLAY_H__
#define __WEATHDATADISPLAY_H__
#include "weatherData.h"
#include "subject.h"
class WeatherDataDisplay:public WeatherDataObserver
{
public:
virtual void update(WeatherData *wd);
//void output();
};
#endif
```c
#include
#include "weatherDataDisplay.h"
void WeatherDataDisplay::update(WeatherData *wd)
{
std::cout<<"temp: "<<wd->getTemp()<<" getPressure"<<wd->getPressure()<<std::endl;
}
用户 main.cpp
#include "weatherData.h"
#include "weatherDataDisplay.h"
int main()
{
WeatherDataDisplay wDD;
WeatherData wd;
wd.registerObserver(&wDD);
wd.setData(12.3,101);
return 0;
}
g++ main.cpp weatherData.cpp weatherDataDisplay.cpp -o main
./main 输出正常。
如果改为闭包,不需要父类指针作为中介传递虚函数。可以直接传递函数对象,函数即是接口。实现一个简单的一对多回调。代码如下,只是举一个例子说明function和bind的功能。这不是一个能在生产中使用的好例子。常用的框架通常使用signal/slot来实现回调。
#include
#include
#include
#include
using namespace std;
class A
{
public:
A(int n):num(n){cout<<"A construct fun"<<endl;}
void print(int x){cout<<"num: "<<num<<" X: "<<x<<endl;}
private:
int num;
};
class SignalTrivial
{
public:
typedef function<void(int)> Functor;
void connect(Functor&& func)
{
functors_.push_back(forward<Functor>(func));
}
void call()
{
for (const Functor & f:functors_)
{
f(10);
}
}
private:
vector<Functor> functors_;
};
int main()
{
A a1(1),a2(2);
// function fprint1 = bind(&A::print,&a1,placeholders::_1);
// function fprint2 = bind(&A::print,&a2,placeholders::_1);
// fprint(3);
SignalTrivial signalTrival;
signalTrival.connect(bind(&A::print,&a1,placeholders::_1));
signalTrival.connect(bind(&A::print,&a1,placeholders::_1));
signalTrival.call();
return 0;
}