问题01:如何初始化成员变量?
你应该总是在构造函数的初始化列表中初始化成员变量,并且避免在构造函数体中进行成员变量的初始化工作。如果这个成员变量是一个类,在初始化列表中进行初始化,只需要一次构造操作;如果在函数体中进行初始化,则需要一次构造和一次赋值操作。此外,初始化列表还可以使你获得自动的异常处理。
引用变量的初始化必须使用初始化列表。根据标准,一个引用必须一直指向一个简单变量,并且不能被改变指向另一个变量。对于非成员变量,编译器要求引用变量在进行定义时必须初始化它,使得它指向某个对象。而对于成员变量,大部分编译都可以接受它直到你创建一个这个类的实例为止。
问题02:如何使用构造函数和析构函数管理资源
在构造函数中分配资源或获取资源,并在析构函数中释放资源,这种技术常常称为资源获取初始化(RAII)。采用这种技术可以减少用户的异常处理代码。
问题03:在不需要类的用户做任何特别处理的情况下,如何在一个容器中存储该类的所有实例
使用一个静态的链表来存储指向对象的指针。当一个对象被创建时,把它的地址加到这个链表中;而当这个对象被销毁时,从这个链表中删除它。
- #include <iostream>
- #include <list>
- #include <algorithm>
- using namespace std;
- class MyClass {
- public:
- MyClass(int val);
- ~MyClass();
- static void showList();
- protected:
- static list<MyClass *> instances_;
- private:
- int val_;
- };
- MyClass::MyClass(int val) :
- val_(val) {
- instances_.push_back(this);
- }
- MyClass::~MyClass() {
- list<MyClass*>::iterator p = find(instances_.begin(), instances_.end(), this);
- if(p != instances_.end())
- instances_.erase(p);
- }
- void MyClass::showList() {
- list<MyClass*>::iterator p = instances_.begin();
- for( ; p != instances_.end(); ++p)
- cout << (*p)->val_ << endl;
- }
- list<MyClass *> MyClass::instances_;
问题04:在运行时,如何动态地查询某个类对象的类型
使用运行时类型标识(RTTI)来查询一个对象的地址以得到这个地址指向的对象类型。不过,RTTI是增加系统开销的。
- #include <iostream>
- #include <typeinfo>
- using namespace std;
- struct Base {};
- struct Derived : public Base {};
- int main()
- {
- Base b;
- Derived d;
- cout << boolalpha << bool(typeid(b) == typeid(d)) << endl;
- cout << typeid(b).name() << endl;
- cout << typeid(d).name() << endl;
- return 0;
- }
问题05:如何确定某个对象的类是否是另一个类的子类
使用dynamic_cast操作符来获取一个类型到另外一个类型的继承关系,dynamic_cast带一个指针或引用参数,并且企图把它转换成它的一个派生类的指针或者引用(downcast)。如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
- #include <iostream>
- #include <typeinfo>
- using namespace std;
- struct Base {};
- struct Derived : public Base {};
- int main()
- {
- Derived d;
- if(dynamic_cast<Base*>(&d))
- cout << "d is derived from Base" << endl;
- else
- cout << "d is not derived from Base" << endl;
- return 0;
- }
问题06:如何实现一个只能被实例化一次的类,即单例模式
创建一个静态成员变量并且这个成员是一个指向当前类的指针,通过private私有修饰符来限制构造函数的使用来创建这个类的对象,并且提供一个公有的静态成员函数来访问这个唯一的实例。
- #include <iostream>
- using namespace std;
- class Singleton {
- public:
- static Singleton* getInstance();
- void setValue(int val);
- int getValue();
- protected:
- int val_;
- private:
- Singleton();
- ~Singleton();
- static Singleton* instance_;
- };
- Singleton* Singleton::getInstance() {
- if(instance_ == NULL)
- instance_ = new Singleton();
- return instance_;
- }
- void Singleton::setValue(int val) { val_ = val;}
- int Singleton::getValue() { return val_;}
- Singleton::Singleton() : val_(0) {}
- Singleton::~Singleton() {}
- Singleton* Singleton::instance_ = NULL;
- int main()
- {
- Singleton* p1 = Singleton::getInstance();
- p1->setValue(1);
- Singleton* p2 = Singleton::getInstance();
- cout << p2->getValue() << endl;
- return 0;
- }
问题07:如何定义一个子类将来可以实现的接口
通过创建一个抽象基类(常常用ABC来称呼)来定义这个接口,客户代码能够用不同的实现来继承这个抽象基类从而保证这个共同的接口。
一个抽象基类是一个不能被实例化的类,因此它也就是起到一个接口的作用。如果一个类声明了最少一个纯虚函数或者它继承了一个纯虚函数但没有它的实现的话,那么这个类就是抽象类。因此,如果这个ABC的一个子类要实例的话,它不得不实现ABC类中所有的虚函数。
最后,如果在你的基类中提供了虚的析构函数,你需要给它提供一个函数体。这是因为子类的析构函数需要自动调用基类的析构函数。
- template <class T>
- class ABC {
- public:
- virtual ~ABC() {}
- virtual void fun1() const = 0;
- virtual void fun2(const T& val) = 0;
- private:
- T val_;
- };
问题08:如何实现一个简单成员函数,这个成员函数可以带一个任意类型的参数
使用成员函数模板并且把这个模板的参数声明成这个成员函数的参数对象类型。调用示例中的get1()函数需要提供模板参数,而调用get2()函数则不需要提供模板参数。因为编译器只能通过形参来推导出模板参数类型,get1()函数没有形参,所以编译器无法自动推导出返回值类型。
- class MyClass {
- public:
- template <typename T>
- T* get1() { return (new T);}
- template <typename T>
- void get2(T*& p) { p = new T;}
- };
问题09:如何调用一个特定类的超类中的函数,这个函数在子类被重写了
使用你的目标基类来修饰你的成员函数名
- Derived *pd;
- pd->Base::fun();