《C++新经典设计模式》之附录A 类和对象
- A.1 静态对象的探讨与全局对象的构造顺序
- A.1.1 静态对象的探讨
- A.1.1.cpp
- A.1.2 全局对象的构造顺序问题
- A.1.2.cpp
- A.2 拷贝构造函数和拷贝赋值运算符
- A.2.1 拷贝构造函数和拷贝赋值运算符的书写
- A.2.1.cpp
- A.2.2 对象自我赋值产生的问题
- A.2.2.cpp
- A.2.3 继承关系下拷贝构造函数和拷贝赋值运算符的书写
- A.2.3.cpp
- A.2.4 拷贝构造函数和拷贝赋值运算符中的重复代码
- A.3 类的public继承(is-a关系)及代码编写规则
- A.3.1 子类遮蔽父类的普通成员函数
- A.3.1.cpp
- A.3.2 父类的纯虚函数接口
- A.3.2.cpp
- A.3.3 父类的虚函数接口
- A.3.3.cpp
- A.3.4 为纯虚函数指定实现体
- A.3.4.cpp
- A.3.5 类的public继承(is-a关系)综合范例
- A.3.5.cpp
- A.3.6 public继承关系下的代码编写规则
- A.3.cpp
- A.4 类与类之间的组合关系与委托关系
- A.4.1 组合关系
- A.4.1.cpp
- A.4.2 委托关系
- A.4.2.cpp
- A.5 类的private继承探讨
- A.5.cpp
- A.6 不能被拷贝构造和拷贝赋值的类对象
- A.6.cpp
- A.7 虚析构函数的内存泄露问题
- A.7.cpp
- A.8 类设计技巧
- A.8.1 提供成员变量的访问接口
- A.8.1.cpp
- A.8.2 避免父类虚函数暴露给子类
- A.8.2.cpp
- A.8.3 构造与析构函数中避免调用虚函数
- A.8.3.cpp
- A.8.4 虚函数的虚与非虚
- A.8.4.cpp
- A.8.5 抽象类的模拟
- A.8.5.cpp
- A.8.6 避免隐式类型转换
- A.8.6.cpp
- A.8.7 强制类对象在堆或栈上分配
- A.8.7.1.cpp
- A.8.7.2.cpp
- A.9 命名空间注意事项
- A.9.cpp
- A.10 类定义的依赖与前向声明
- A.10.cpp
A.1 静态对象的探讨与全局对象的构造顺序
A.1.1 静态对象的探讨
类中类类型的静态成员变量,即使未被使用,也会被构造和析构。
inline static 类类型的成员变量;会同时声明和定义
A.1.1.cpp
#include <iostream>
using namespace std;namespace ns1
{class A{int m_i;public:A() { cout << "ns1::A::A()\n"; }~A() { cout << "ns1::A::~A()\n"; }};class B{public:static A m_sa; // 类B中静态成员变量(类类型A)m_sa未使用,可以只声明不定义};
}namespace ns2
{class A{public:int m_i;A() { cout << "ns2::A::A()\n"; }~A() { cout << "ns2::A::~A()\n"; }};class B{public:static A m_sa; // 声明};// 类B中静态成员变量(类类型A)m_sa使用,必须定义// m_sa定义时,未使用也会构造和析构A B::m_sa;
}namespace ns3
{class A{public:A() { cout << "ns3::A::A()\n"; }~A() { cout << "ns3::A::~A()\n"; }};class B{public:inline static A m_sa; // inline静态成员变量,同时声明和定义,m_sa即使未使用,也会构造和析构// inline static int a;//简单类型,未使用时可能不会分配内存};
}namespace ns4
{class A{public:A() { cout << "ns4::A::A()\n"; }~A() { cout << "ns4::A::~A()\n"; }};// 函数中类类型的静态对象,函数未调用,则不会被构造// 函数调用多次,只会被构造一次void myfunc(){static A aboj;}
}int main()
{
#if 0 ns1::B bobj;
#endif#if 0 ns2::B bobj;cout << bobj.m_sa.m_i << endl;
#endif#if 0 ns3::B bobj;
#endif#if 1using namespace ns4;myfunc();myfunc();
#endifcout << "Over!\n";return 0;
}
A.1.2 全局对象的构造顺序问题
函数中静态对象在函数第一次执行到时仅初始化一次。
全局对象的的初始化顺序无法保证,C++只保证特定编译单元(*.cpp)中全局对象的的初始化顺序。
A.1.2.cpp
#include <iostream>
using namespace std;class A2
{
public:int m_i;A2() : m_i(5) { cout << "A2::A2()\n"; }~A2() { cout << "A2::~A2()\n"; }
};A2 &getA2Obj()
{static A2 a2;return a2;
}class A1
{
public:A1(){cout << getA2Obj().m_i << endl;cout << "A1::A1()\n";}~A1(){cout << "A1::~A1()\n";}
};int main()
{{A1 a1;}cout << "Over!\n";return 0;
}
A.2 拷贝构造函数和拷贝赋值运算符
A.2.1 拷贝构造函数和拷贝赋值运算符的书写
A.2.1.cpp
#include <iostream>
using namespace std;class A
{
public:int m_caa;int m_cab;A() : m_caa(0), m_cab(0) { cout << "A::A()\n"; }~A() { cout << "A::~A()\n"; }A(const A &tmpobj) // 拷贝构造函数{cout << "A::A(const A&)\n";m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}A &operator=(const A &tmpobj) // 拷贝赋值运算符{cout << "A& A::operator=(const A&)\n";m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}
};void test(const A &a) { cout << "test(const A &a)\n"; }int main()
{{A aobj1;aobj1.m_caa = 10;aobj1.m_cab = 20;A aobj2 = aobj1; // 调用拷贝构造函数aobj2 = aobj1; // 调用拷贝赋值运算符}cout << "Over!\n";return 0;
}
A.2.2 对象自我赋值产生的问题
A.2.2.cpp
#include <iostream>
#include <cstring>
using namespace std;namespace ns1
{class A{public:int m_caa;int m_cab;char *m_cap;A() : m_caa(0), m_cab(0), m_cap(new char[100]) { cout << "A::A()\n"; }~A(){cout << "A::~A()\n";delete[] m_cap;}A(const A &tmpobj) // 拷贝构造函数{cout << "A::A(const A&)\n";common(tmpobj);}A &operator=(const A &tmpobj) // 拷贝赋值运算符{cout << "A& A::operator=(const A&)\n";if (this != &tmpobj){delete m_cap;common(tmpobj);}return *this;}private:void common(const A &tmpobj){m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap, 100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}};
}namespace ns2
{class A{public:int m_caa;int m_cab;char *m_cap;A() : m_caa(0), m_cab(0), m_cap(new char[100]) { cout << "A::A()\n"; }~A(){cout << "A::~A()\n";delete[] m_cap;}A(const A &tmpobj) // 拷贝构造函数{cout << "A::A(const A&)\n";m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap, 100);m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;}A &operator=(const A &tmpobj) // 拷贝赋值运算符{cout << "A& A::operator=(const A&)\n";char *ptmp = new char[100];memcpy(ptmp, tmpobj.m_cap, 100);delete m_cap;m_cap = ptmp;m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;return *this;}};
}int main()
{
#if 0{ns1::A aobj2;strcpy(aobj2.m_cap, "abcdefg");aobj2 = aobj2; // 调用拷贝赋值运算符}
#endif#if 1{ns2::A aobj2;strcpy(aobj2.m_cap, "abcdefg");aobj2 = aobj2; // 调用拷贝赋值运算符}
#endifcout << "Over!\n";return 0;
}
A.2.3 继承关系下拷贝构造函数和拷贝赋值运算符的书写
如果子类中写了拷贝构造函数或者拷贝赋值运算符,
那么父类中的拷贝构造函数和拷贝赋值运算需要自己调用
编译器不会自动调用父类中对应的拷贝构造函数和拷贝赋值运算符
A.2.3.cpp
#include <iostream>
#include <cstring>
using namespace std;namespace ns1
{class A{public:int m_caa;int m_cab;char *m_cap; // 指向定长100个字节的内存A() : m_caa(0), m_cab(0), m_cap(new char[100]) { cout << "A::A()\n"; }virtual ~A(){cout << "A::~A()\n";delete[] m_cap;}A(const A &tmpobj) // 拷贝构造函数{cout << "A::A(const A&)\n";m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap, 100);}A &operator=(const A &tmpobj) // 拷贝赋值运算符{cout << "A::A& operator=(const A&)\n";if (this != &tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;memcpy(m_cap, tmpobj.m_cap, 100);}return *this;}};class B : public A{};
}namespace ns2
{class A{public:int m_caa;int m_cab;char *m_cap; // 指向定长100个字节的内存A() : m_caa(0), m_cab(0), m_cap(new char[100]) { cout << "A::A()\n"; }virtual ~A(){cout << "A::~A()\n";delete[] m_cap;}A(const A &tmpobj) // 拷贝构造函数{cout << "A::A(const A&)\n";m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;m_cap = new char[100];memcpy(m_cap, tmpobj.m_cap, 100);}A &operator=(const A &tmpobj) // 拷贝赋值运算符{cout << "A::A& operator=(const A&)\n";if (this != &tmpobj){m_caa = tmpobj.m_caa;m_cab = tmpobj.m_cab;memcpy(m_cap, tmpobj.m_cap, 100);}return *this;}};class B : public A{public:B() { cout << "B::B()\n"; }~B() { cout << "B::~B()\n"; }B(const B &tmpobj) : A(tmpobj) // 拷贝构造函数,调用了类A的拷贝构造函数{cout << "B::B(const B&)\n";// A(tmpobj); //意图调用类A的拷贝构造函数,存在二义性,函数调用或者对象定义(A tmpobj)。}B &operator=(const B &tmpobj) // 拷贝赋值运算符{if (this != &tmpobj){A::operator=(tmpobj); // 调用类A的拷贝赋值运算符cout << "B::B& operator=(const B&)\n";}return *this;}};
}int main()
{
#if 0{using namespace ns1;B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap, "new class");B bobj2 = bobj1; // 执行类A的拷贝构造函数bobj2 = bobj1; // 执行类A的拷贝赋值运算符}
#endif#if 1{using namespace ns2;B bobj1;bobj1.m_caa = 100;bobj1.m_cab = 200;strcpy(bobj1.m_cap, "new class");B bobj2 = bobj1; // 执行拷贝构造函数bobj2 = bobj1; // 执行拷贝赋值运算符bobj2 = bobj2;}
#endifcout << "Over!\n";return 0;
}
A.2.4 拷贝构造函数和拷贝赋值运算符中的重复代码
重复代码写成private函数去调用。
A.3 类的public继承(is-a关系)及代码编写规则
public继承关系的检验规则:能够在父类对象上做的行为也必然能在子类对象上做,每个子类对象同时也都是一个父类对象。
里氏替换(Liskov替换)原则:任何父类出现的地方都应该可以无差别的使用子类替换。
A.3.1 子类遮蔽父类的普通成员函数
public继承中不建议子类遮蔽父类的普通成员函数,这样子类就会有不同的行为。
A.3.1.cpp
#include <iostream>
using namespace std;class Human
{
public:virtual ~Human() {}void eat() { cout << "human eat food" << endl; }
};class Man : public Human // 公有继承
{
public:void eat(){cout << "man eat food" << endl;}
};int main()
{Man myman;myman.eat();myman.Human::eat();cout << "Over!\n";return 0;
}
A.3.2 父类的纯虚函数接口
纯虚函数的父类变成抽象类,不能生成该类对象。
从抽象类继承的子类,必须实现该接口。
A.3.2.cpp
#include <iostream>
using namespace std;class Human
{
public:virtual ~Human() {}virtual void work() = 0; // 纯虚函数,抽象基类
};class Man : public Human // 公有继承
{
public:virtual void work() override { cout << "heavy work" << endl; }
};class Woman : public Human
{
public:virtual void work() override { cout << "light work" << endl; }
};int main()
{Man myman;myman.work();Woman mywoman;mywoman.work();cout << "Over!\n";return 0;
}
A.3.3 父类的虚函数接口
父类的虚函数接口让子类继承成员函数接口和实现,同时子类可以提供自己新的实现。
A.3.3.cpp
#include <iostream>
using namespace std;class Human
{
public:virtual ~Human() {}virtual void avlf() { cout << "about 75" << endl; } // 普通虚函数
};class Man : public Human // 公有继承
{
public:virtual void avlf() override { cout << "about 72" << endl; }
};class Woman : public Human
{
public:virtual void avlf() { cout << "about 80" << endl; }
};int main()
{Man myman;myman.avlf();Woman mywoman;mywoman.avlf();cout << "Over!\n";return 0;
}
A.3.4 为纯虚函数指定实现体
纯虚函数强制子类去实现。
父类中纯虚函数增加实现体,方便子类直接调用。
A.3.4.cpp
#include <iostream>
using namespace std;class Human
{
public:virtual ~Human() {}virtual void avlf() = 0; // 纯虚函数,强制子类实现
};
// 增加实现体,可用于子类调用
void Human::avlf() { cout << "about 75" << endl; }class Man : public Human // 公有继承
{
public:virtual void avlf() override { cout << "about 72" << endl; }
};class Woman : public Human
{
public:virtual void avlf() { Human::avlf(); }
};int main()
{Man myman;myman.avlf();Woman mywoman;mywoman.avlf();cout << "Over!\n";return 0;
}
A.3.5 类的public继承(is-a关系)综合范例
A.3.5.cpp
#include <iostream>
using namespace std;class A
{
};
class B : public A
{
};
class C : public B
{
};
void myfunc(A tmpa)
{cout << "myfunc(A tmpa)" << endl;
}
/*
void myfunc(B tmpb)
{cout << "myfunc(B tmpb)" << endl;
}
*/
void myfunc(C tmpc)
{cout << "myfunc(C tmpc)" << endl;
}int main()
{A myobja;myfunc(myobja);B myobjb;myfunc(myobjb);C myobjc;myfunc(myobjc);cout << "Over!\n";return 0;
}
A.3.6 public继承关系下的代码编写规则
- 父类的普通成员函数,子类不应该覆盖。
- 父类的纯虚函数,只定义接口,具体实现子类完成。
- 父类的普通虚函数,定义接口并编写实现,子类可编写实现。
- 能非public继承时,就不要使用public继承。
A.3.cpp
#include <iostream>
using namespace std;namespace ns1
{class Human{public:Human() { cout << "Human::Human()" << endl; }virtual ~Human() { cout << "Human::~Human()" << endl; }void eat() { cout << "human eat food" << endl; }virtual void avlf() { cout << "about 75" << endl; } // 普通虚函数virtual void work() = 0; // 纯虚函数,抽象基类};class Man : public Human // 公有继承{public:Man() { cout << "Man::Man()" << endl; }~Man() { cout << "Man::~Man()" << endl; }void eat(){Human::eat(); // 调用父类的同名函数cout << "man eat food" << endl;}virtual void work() override { cout << "heavy work" << endl; }virtual void avlf() override { cout << "about 72" << endl; }};class Woman : public Human{public:Woman() { cout << "Woman::Woman()" << endl; }~Woman() { cout << "Woman::~Woman()" << endl; }virtual void work() override { cout << "light work" << endl; }virtual void avlf() override{Human::avlf(); // 调用父类的同名函数cout << "about 80" << endl;}};
}namespace ns2
{class Human{public:virtual ~Human() {}void eat() { cout << "human eat food" << endl; }virtual void avlf() = 0; // 纯虚函数,强制子类实现virtual void work() = 0; // 纯虚函数};void Human::avlf() // 实现,可用于子类调用{cout << "about 75" << endl;}class Man : public Human // 公有继承{public:virtual void avlf() override{Human::avlf(); // 调用父类的同名函数cout << "about 72" << endl;}virtual void work() override { cout << "heavy work" << endl; }};class Woman : public Human{public:virtual void avlf() override{Human::avlf(); // 调用父类的同名函数cout << "about 80" << endl;}virtual void work() override { cout << "light work" << endl; }};
}namespace ns3
{class A{};class B : public A{};class C : public B{};void myfunc(A tmpa){cout << "myfunc(A tmpa)" << endl;}/*void myfunc(B tmpb){cout << "myfunc(B tmpb)" << endl;}*/void myfunc(C tmpc){cout << "myfunc(C tmpc)" << endl;}
}int main()
{
#if 0{ns1::Man myman;myman.eat();myman.Human::eat();}
#endif#if 0{ns2::Woman mywoman;mywoman.avlf();}
#endif#if 1{using namespace ns3;A myobja;myfunc(myobja);B myobjb;myfunc(myobjb);C myobjc;myfunc(myobjc);}
#endifcout << "Over!\n";return 0;
}
A.4 类与类之间的组合关系与委托关系
A.4.1 组合关系
一个类的定义中含有其它类类型成员变量。
A.4.1.cpp
#include <iostream>
#include <map>
#include <string>
#include <memory>
using namespace std;namespace ns1
{class Info{string m_name; // 名字int m_gender; // 性别int m_age; // 年龄};class Human{public:Info m_info;};
}namespace ns2
{template <typename T, typename U>class smap{multimap<T, U> container;public:void insert(const T &key, const U &value){auto iter = container.find(key);if (iter == container.end())container.insert(make_pair(key, value));elseiter->second = value;}U getValue(const T &key) const{auto iter = container.find(key);return iter->second;}size_t size() const { return container.size(); }};
}int main()
{
#if 0ns1::Human myhuman;#endif#if 1{multimap<int, int> tmpmpc;tmpmpc.insert(make_pair(1, 1));tmpmpc.insert(make_pair(2, 3));tmpmpc.insert(make_pair(1, 2));cout << "tmpmpc.size()=" << tmpmpc.size() << endl;cout << tmpmpc.count(1) << endl;for (auto &e : tmpmpc)cout << e.first << "--->" << e.second << endl;cout << endl;ns2::smap<int, int> tmpsmap;tmpsmap.insert(1, 1);tmpsmap.insert(2, 3);tmpsmap.insert(1, 2);cout << "tmpsmap.size()=" << tmpsmap.size() << endl;cout << tmpsmap.getValue(1) << endl;}
#endifcout << "Over!\n";return 0;
}
A.4.2 委托关系
一个类中包含指向另一个类的指针。
A.4.2.cpp
#include <iostream>
#include <map>
#include <string>
#include <memory>
using namespace std;namespace ns1
{class A{public:void funca() {}};class B{A *m_pa;public:B(A *tmpa = nullptr) : m_pa(tmpa) {}void funcb(){if (m_pa != nullptr)m_pa->funca();}};
}namespace ns2
{class A{public:void funca() {}};class B{shared_ptr<A> m_pa;public:B(const shared_ptr<A> &tmpa = nullptr) : m_pa(tmpa) {}void funcb(){if (m_pa != nullptr)m_pa->funca();}};
}int main()
{#if 0{using namespace ns1;A *pa = new A();B *pb = new B(pa);pb->funcb();delete pb;delete pa;}
#endif#if 1{using namespace ns2;shared_ptr<A> pa(new A());shared_ptr<B> pb(new B(pa));pb->funcb();}
#endifcout << "Over!\n";return 0;
}
A.5 类的private继承探讨
- private继承,是一种组合关系,确切的说,是组合关系中的 is-implemented-in-terms-of关系(根据…实现出)。
- 一般优先考虑使用组合关系,只有在一些比较特殊的情况和必要的情况下,比如牵扯一些protected成员、private成员、虚函数等,才考虑到用private来表达组合关系。
A.5.cpp
#include <iostream>
#include <map>
using namespace std;namespace ns1
{class Human{public:virtual ~Human() { cout << "~Human()" << endl; }Human() { cout << "Human()" << endl; }};class Man : public Human{public:~Man() { cout << "~Man()" << endl; }Man() { cout << "Man()" << endl; }};class A{public:~A() { cout << "~A()" << endl; }A() { cout << "A()" << endl; }A(const A &) { cout << "A(const A&)\n"; }A &operator=(const A &tmpobj){if (this != &tmpobj)cout << "A &operator=(const A&)\n";return *this;}};class B : public A{public:~B() { cout << "~B()" << endl; }B() { cout << "B()" << endl; }B(const B &tmpobj) : A(tmpobj) { cout << "B(const B&)\n"; }B &operator=(const B &tmpobj){if (this != &tmpobj){A::operator=(tmpobj);cout << "B &operator=(const B&)\n";}return *this;}};void f(A aobj) { cout << "f(A aobj)" << endl; }
}namespace ns2
{class Human{public:virtual ~Human() {}};class Man : private Human{};class A{};class B : private A{};void f(A aobj) {}
}namespace ns3
{消息队列类class MsgQueue{//...入消息队列,出消息队列};class Timer{public:Timer(int inttimems); // inttimems:间隔多少毫秒调用一次CallBackvirtual void CallBack(); // 一个到时间后会被调用的虚函数//....定时器的编写,有一定复杂度,这里不详细探讨};消息队列类class MsgQueue2 : private Timer{//...入消息队列,出消息队列// 构造函数等....private:virtual void CallBack(); // 一个到时间后会被调用的虚函数};
}int main()
{
#if 0{using namespace ns1;Man myman;Human &myhuman = myman; // 父类引用绑定子类对象Human *myhuman2 = new Man(); // 父类指针指向子类对象delete myhuman2;B bobj;f(bobj);A aobj; //aobj = bobj;}
#endif#if 1{using namespace ns2;Man myman;//Human &myhuman = myman; // error// Human *myhuman2 = new Man(); // errorB bobj;// f(bobj); // error// A aobj = bobj; // error}
#endifcout << "Over!\n";return 0;
}
A.6 不能被拷贝构造和拷贝赋值的类对象
不能被拷贝构造的类对象具有实际意义。
CWindLock类,构造函数进入临界区,析构函数离开临界区,加锁,解锁,显然不应该允许被复制。
A.6.cpp
#include <iostream>
#include <map>
using namespace std;namespace ns1
{class A{};
}namespace ns2
{class A{public:A() {} // 因为编写了拷贝构造或者拷贝赋值(禁止),必须提供一个构造函数A(const A &) = delete;//禁止拷贝构造A &operator=(const A &) = delete;//禁止拷贝赋值运算符};
}namespace ns3
{class A{private: // 成员函数或者友元函数能够访问这两个函数A(const A &) {}A &operator=(const A &) { return *this; }public:A() {} // 必须提供构造函数void func(const A &aobj) // 成员函数或者友元函数能够执行拷贝构造函数和拷贝赋值运算符{*this = aobj; // 拷贝赋值A aobj2(aobj); // 拷贝构造}};
}namespace ns4
{class A{private://只保留函数声明,编译链接时会报告链接错误A(const A &);A &operator=(const A &);public:A() {} // 必须提供构造函数void func(const A &aobj) // 成员函数或者友元函数能够执行拷贝构造函数和拷贝赋值运算符{*this = aobj; // 拷贝赋值A aobj2(aobj); // 拷贝构造}};
}namespace ns5
{class noncopyable{private://只声明,未实现,子类拷贝构造和赋值时无法调用。noncopyable(const noncopyable &);noncopyable &operator=(const noncopyable &);protected: // 只允许本类和子类成员访问noncopyable(){};~noncopyable(){};};// noncopyable不能被拷贝,子类也不行//A对象具备noncopyable对象的不允许拷贝的特性,组合关系,private继续,非public继承class A : private noncopyable // 根据...实现出:组合关系(通过noncopyable类实现出A类){public:void func(const A &aobj){//*this = aobj; //禁止调用拷贝赋值运算符// A aobj2(aobj); //禁止调用拷贝构造函数}};
}int main()
{
#if 0{using namespace ns1;A aobj1; // 构造A aobj2(aobj1); // 拷贝构造A aobj3 = aobj2; // 拷贝构造A aobj4;aobj4 = aobj3; // 拷贝赋值}
#endif#if 0{using namespace ns2;A aobj1;// A aobj2(aobj1); //禁止拷贝构造A aobj3;// aobj3 = aobj1; //禁止拷贝赋值}
#endif#if 0{using namespace ns3;A aobj1;A aobj3;aobj3.func(aobj1);}
#endif#if 0{using namespace ns4;A aobj1;A aobj3;// aobj3.func(aobj1);//未实现拷贝构造和拷贝赋值,编译连接时报错}
#endif#if 1{using namespace ns5;A aobj1;// A aobj2(aobj1); //禁止拷贝构造A aobj3;// aobj3 = aobj1; //禁止拷贝赋值}
#endifcout << "Over!\n";return 0;
}
A.7 虚析构函数的内存泄露问题
-
父类指针指向子类时,若父类析构函数非虚,则子类的析构函数不执行(父类虚析构函数中会自动调用子类的析构函数)。
-
虚函数,虚函数表,多出4或8字节。(不做父类,不应该有虚函数)
-
不要随便public继承一个自己不熟悉的类(若父类无virtual析构函数,delete子类时,子类析构函数可能不会被调用)
-
C++11 final,不希望该类被继承
只有父类指针指向子类对象(父类引用绑定子类对象)这种多态形式的代码存在的时候,父类才有必要写一个虚析构函数(public修饰)。
- a)如果子类public继承父类,那么父类指针可以指向子类对象
- b)如果子类private或者protected继承父类,那么父类指针不可以指向子类对象
- c)如果让父类指针指向子类对象,那么就需要用public继承,这个时候父类就需要提供虚析构函数。
总结类中存在虚析构函数的情形:
- a)一般如果父类中有其它虚函数,意味着会按照多态的方式来使用该父类,也就是一般会存在父类指针指向子类对象的情形, 那么此时,父类中应该有一个public修饰的虚析构函数。
- b)如果代码中并不会出现父类指针指向子类对象(父类引用绑定子类对象)的多态情形,那么父类中不需要有虚析构函数。同时,在文档中应该明确告知开发者不应该public继承该类,而应该尽量用private。
- c)如果某个类并不作为父类使用,那么不应该在该类中存在虚析构函数。
A.7.cpp
#include <iostream>
#include <memory>
using namespace std;namespace ns1
{class noncopyable{private:noncopyable(const noncopyable &);noncopyable &operator=(const noncopyable &);protected: // 只允许本类和子类成员访问noncopyable(){};~noncopyable(){};};class A : public noncopyable{};class ThirdPartClass{public:ThirdPartClass(){cout << "ThirdPartClass:ThirdPartClass()\n";}~ThirdPartClass(){cout << "ThirdPartClass:~ThirdPartClass()\n";}};class B : public ThirdPartClass{public:char *m_p;B(){cout << "B:B()\n";m_p = new char[100];}~B(){cout << "B:~B()\n";delete m_p;}};
}namespace ns2
{class noncopyable{private:noncopyable(const noncopyable &);noncopyable &operator=(const noncopyable &);protected: // 只允许本类和子类成员访问noncopyable(){};~noncopyable(){};};class A : private noncopyable{public:A() { cout << "A:A()\n"; }~A() { cout << "A:~A()\n"; }};class ThirdPartClass{public:ThirdPartClass(){cout << "ThirdPartClass:ThirdPartClass()\n";}virtual ~ThirdPartClass(){cout << "ThirdPartClass:~ThirdPartClass()\n";}};class B : public ThirdPartClass{public:char *m_p;B(){cout << "B:B()\n";m_p = new char[100];}~B(){cout << "B:~B()\n";delete m_p;}};
}int main()
{
#if 0{using namespace ns1;ThirdPartClass *ptp = new B(); // 父类指针指向子类对象delete ptp; // 父类无虚析构函数,子类析构函数不会被调用(内存泄漏)}
#endif#if 0{using namespace ns1;// 如果子类private或者protected继承父类,那么父类指针不可以指向子类对象noncopyable *pnca = new A();// delete pnca;//error,pnca的析构函数是protected类型的,无法外部被调用}
#endif#if 0{using namespace ns2;A *pa = new A();delete pa;shared_ptr<A> pb(new A());}
#endif#if 1{using namespace ns2;ThirdPartClass *pa = new B();delete pa;cout << endl;B *pb = new B();delete pb;cout << endl;shared_ptr<ThirdPartClass> pc(new B());shared_ptr<B> pd(new B());}
#endifcout << "Over!\n";return 0;
}
A.8 类设计技巧
A.8.1 提供成员变量的访问接口
当需要队成员变量进行访问时,可以使用private来修饰成员变量,然后提供一个public修饰的成员函数作为外界访问该成员变量的接口。
A.8.1.cpp
#include <iostream>
using namespace std;namespace ns1
{class A1{public:int m_a;};class A2{int m_a;public:int &getA() { return m_a; }};
}int main()
{
#if 0{using namespace ns1;A1 a1obj;a1obj.m_a = 3;cout << a1obj.m_a << endl;A2 a2obj;a2obj.getA() = 5;cout << a2obj.getA() << endl;}
#endifcout << "Over!\n";return 0;
}
A.8.2 避免父类虚函数暴露给子类
如果能将虚函数设置为私有,则优先考虑将其设置为私有。
A.8.2.cpp
#include <iostream>
using namespace std;namespace ns1
{class A{private:// public:virtual void myvirfunc() { cout << "A::myvirfunc()" << endl; }public:virtual ~A() {}void myfunc() { myvirfunc(); }};class B : public A{private:virtual void myvirfunc() override { cout << "B::myvirfunc()" << endl; }};
}int main()
{
#if 1{using namespace ns1;A *pobj = new B();pobj->myfunc();// pobj->A::myvirfunc();delete pobj;}
#endifcout << "Over!\n";return 0;
}
A.8.3 构造与析构函数中避免调用虚函数
设想有父子关系的两个类:
- a)如果在父类的构造函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的构造函数体时对象的子类部分还没被构造出来(未成熟的对象)。
- b)如果在父类的析构函数中调用了一个子类的虚函数是无法做到的,因为执行到父类的析构函数体时对象的子类部分其实已经销毁了。
不要在类的构造函数和析构函数中调用虚函数,在构造函数和析构函数中,虚函数可能会失去虚函数的作用而被当做普通函数看到。
A.8.3.cpp
#include <iostream>
using namespace std;class ANew
{
public:ANew() { f1(); }virtual ~ANew() { f2(); }// 定义两个虚函数virtual void f1() { cout << "virtual ANew::f1()" << endl; }virtual void f2() { cout << "virtual ANew::f2()" << endl; }// 定义普通成员函数,调用虚函数void AClassFunc() { f1(); }
};class BNew : public ANew
{
public:BNew() { f1(); }virtual ~BNew() { f2(); }// 定义两个虚函数virtual void f1() override { cout << "virtual BNew::f1()" << endl; }virtual void f2() override { cout << "virtual BNew::f2()" << endl; }
};int main()
{ANew *pnew = new BNew();cout << "-----------begin-----------" << endl;pnew->f1();pnew->f2();pnew->AClassFunc();cout << "-----------end-----------" << endl;delete pnew;cout << "Over!\n";return 0;
}
A.8.4 虚函数的虚与非虚
-
父类析构函数不一定非是虚函数。但是当父类指针指向子类对象(父类引用绑定子类对象)这种多态形式的代码存在时,父类是需要写一个public修饰的虚析构函数的,这样就可以通过父类的接口来多态的销毁子类对象,否则就可能会造成内存泄漏。
-
noncopyable,其析构函数是用protected修饰的;而且noncopyable的析构函数并不是虚函数,仅仅用protected修饰一下析构函数就达到了几个效果:
a)无法创建父类对象
b)无法让父类指针指向父类或者子类对象(因为无法成功delete) -
a)如果一个父类的析构函数不是虚函数,并且也不利用这个父类创建对象,也不会用到这个父类类型的指针,则应该考虑将该父类的析构函数使用protected而非public来修饰以防止写出错误的代码(增加代码编写安全性,防止误用。
-
b)其实,父类的析构函数不是虚函数,本身就暗示着不会通过父类的接口来多态的销毁子类对象,也暗示着不会用到父类类型的指针。
A.8.4.cpp
#include <iostream>
using namespace std;class ANew2
{
protected:~ANew2() {}
};
class BNew2 : public ANew2
{
};int main()
{// ANew2 aobj;//error,析构函数非publicANew2 *paobj = new ANew2();// delete paobj;//error,无法delete,ANew2指针ANew2 *paobj2 = new BNew2();// delete paobj2;//error,无法delete,ANew2指针// 子类能够调用protected的父类析构函数BNew2 bobj;BNew2 *pbobj = new BNew2();delete pbobj;cout << "Over!\n";return 0;
}
A.8.5 抽象类的模拟
抽象类要求至少有一个纯虚函数(接口规范),不能用来生成对象,统一管理子类。
- 将模拟的抽象类的构造函数和拷贝构造函数都使用protected来修饰。
- 将模拟的抽象类的析构函数设置为纯虚函数并在类定义之外为该纯虚函数定义函数体。
- 将模拟的抽象类的析构函数使用protected来修饰。
A.8.5.cpp
#include <iostream>
using namespace std;namespace ns1
{class PVC{protected:PVC(){}; // 构造函数PVC(const PVC &){}; // 拷贝构造函数};class SubPVC : public PVC{};
}namespace ns2
{class PVC{public:virtual ~PVC() = 0;};// 类外实现类析构函数(绝大部分纯虚函数没有实现体,但纯虚析构函数是个特例,为了释放资源等,所以一般要有实现体。// 子类析构函数会隐式地调用父类的析构函数PVC::~PVC() {}class SubPVC : public PVC{public:~SubPVC() // 编译器会插入调用父类析构函数的代码{}};
}namespace ns3
{class PVC{protected:~PVC(){};};class SubPVC : public PVC{};
}int main()
{
#if 0{using namespace ns1;SubPVC mysubobj1;SubPVC mysubobj2(mysubobj1); // 子类能够调用protected的父类构造函数// protected构造函数的类无法创建// PVC myobj1;// PVC myobj2(myobj1);}
#endif#if 0{using namespace ns2;SubPVC mysubobj; // 析构时编译器会先执行该类自己的析构函数体,再执行父类的析构函数体PVC *p = new SubPVC();delete p;}
#endif#if 1{using namespace ns3;SubPVC mysubobj1;SubPVC mysubobj2(mysubobj1);SubPVC *pobj = new SubPVC();delete pobj;// protected析构函数,无法创建类// PVC myobj1;// PVC myobj2(myobj1);PVC *p1 = new PVC();// delete p1;//protected析构函数,无法deletePVC *p2 = new SubPVC();// delete p2;//protected析构函数,无法delete}
#endifcout << "Over!\n";return 0;
}
A.8.6 避免隐式类型转换
A.8.6.cpp
#include <iostream>
using namespace std;class A
{
public: // explicit避免隐射类型转换explicit A(int i) // 类型转换构造函数,一个形参(为待转换的数据类型),非本类的const引用{cout << i << endl;}
};int main()
{// 编译器通过构造函数把数字15转换成一个A类型的类对象(临时对象)并构造在aobj对象预留的空间里。// A aobj = 15; //explicit禁止隐形类型转换A aobj = A(15);A aobj2(20);cout << "Over!\n";return 0;
}
A.8.7 强制类对象在堆或栈上分配
- 类对象不可以在堆上分配内存
重载类中的operator new和operator delete操作符。
A.8.7.1.cpp
#include <iostream>
using namespace std;class A
{
private:static void *operator new(size_t size);static void operator delete(void *phead);static void *operator new[](size_t size);static void operator delete[](void *phead);
};int main()
{A paobj;A paobjarray[3];// 禁止堆上分配// A *paobj = new A();// delete paobj;// A *paobjarray = new A[3]();// delete[] paobjarray;cout << "Over!\n";return 0;
}
- 强制类对象只可以在堆上分配内存
A.8.7.2.cpp
#include <iostream>
using namespace std;class A
{
private:~A() {}public:void destroy(){delete this;}
};int main()
{// A aobj;//禁止栈上分配A *paobj = new A();// delete paobj;paobj->destroy(); // 释放内存cout << "Over!\n";return 0;
}
A.9 命名空间注意事项
- using声明的命名空间代码行不要放在.h头文件中。
- .cpp文件中using声明的命名空间代码行放在所有#include语句行后面。
A.9.cpp
#include <iostream>
using namespace std; // 声明std命名空间namespace a1nsp
{class A{};
}
using namespace a1nsp; // 声明a1nsp命名空间namespace a2nsp
{class A{};
}
using namespace a2nsp; // 声明a2nsp命名空间int main()
{// A aobj; //名字冲突a1nsp::A aobj;cout << "Over!\n";return 0;
}
A.10 类定义的依赖与前向声明
-
类前向声明解决类相互依赖的问题。
-
类A1和类A2直接依赖。一般避免这种设计,而是 引入一个新类,让A1和A2类都依赖于这个新类,从而打破这种类A1和A2的依赖关系。
-
类的前向声明并不是类的完整定义
有些情况下,必须要类的完整定义而不是类的前向声明
- a)在类A1的定义中加入A2类型的对象。
- b)在类A1的定义中需要知道A2类型对象的大小。
- c)在类A1的定义中需要调用类A2的成员函数时。
A.10.cpp
#include <iostream>
using namespace std;class A1; // 类A1的前向声明
class A2
{
public:A1 *m_pa1;void a2func() {}
};class A1
{
public:A2 *m_pa2;void a1func_1(){int tmpvalue = sizeof(A2); // 使用了未定义类型“A2”}void a1func_2(){m_pa2->a2func(); // 使用了未定义类型“A2”}
};int main()
{cout << "Over!\n";return 0;
}