Effective C++ 读书笔记
文章目录
基类构造函数中禁止调用子类可能重写的方法
基类构造or析构中调用子类virtual的函数100%出错:基类有实现的实际调用基类,基类没有实现的报踩内存。因为子类(derived classes)现在还没有构造。
基类构造函数中,只能调用非virtual的方法。
一般这种情况是想基类子类之间有信息的传递(父类传给子类)。可以换成信息从子类传递给基类:
class Base {
public:
explicit Base(const std::string &info) {
Log(info); // 非virtual调用
}
void Log(const std::string &info) { // 非virtual函数
cout << "Base Log: " << info << endl;
}
};
class Derived : public Base {
public:
Derived (const std::string param) : Base(param) { // 子类构造的时候,传递给基类构造
cout << "Derived construct: " << param << endl;
}
};
// 打印顺序仍为基类构造,子类构造。
Base Log: new Derived
Derived construct: new Derived
Base Log: log
拷贝构造和拷贝运算符,要拷贝完全
子类的拷贝构造需要兼顾基类的拷贝,调用基类的有参构造
Derived::Derived(const Derived& derived) : Base(derived) {} // 调用基类的copy构造
Derived& Derived::operator=(const Derived& derived) {
Base::operator=(derived); // 基类成员进行赋值
return *this;
}
- 复制自己的成员变量
- 调用所有基类的复制函数
以独立语句将new对象放入智能指针
func1以下调用可能会出现内存泄露:
func1(std::shared_ptr<ObjA>(new ObjA), func2());
调用func1之前,编译器创建代码做三件事情,在执行顺序如下时:
- 执行 new ObjA
- 调用func2()
- 调用std::shared_ptr构造函数
如果func2调用出现异常,new ObjA返回的指针遗失了,会产生内存泄漏。
修改为:
std::shared_ptr<ObjA> p(new ObjA);
func1(p, func2());
传递引用or传递值
class Base {
public:
explicit Base(const std::string &info) {
Log(info);
}
virtual void Log(const std::string &info) const {
cout << "Base Log: " << info << endl;
}
};
class Derived : public Base {
public:
Derived (const std::string ¶m) : Base(param) {
}
virtual void Log(const std::string &info) const {
cout << "Derived Log: " << info << endl;
}
};
上面的定义,如果Log方法传递值,会导致子类的virtual函数实现还是指向了基类,这种子类信息缺失的情况为,切割
void Log(Base cl) {
cl.Log("log func");
}
// 调用
Base b("base obj");
Derived d("Derived obj");
Log(b);
Log(d);
// 输出
Base Log: base obj
Base Log: Derived obj
Base Log: log func
Base Log: log func
传递引用,实际上传递的是指针,会避免这种情况
void Log(const Base &cl) {
cl.Log("log func");
}
Base Log: base obj
Base Log: Derived obj
Base Log: log func
Derived Log: log func
- 内置类型、std的迭代器、函数对象例外。
- 返回新对象时,不能返回指向一个堆对象(可能会有内存泄露),或者局部栈对象(已销毁)。正确的是返回一个新对象:
Base func(const std::string ¶m) {
return Base(param);
}
类型转换
const_cast
将 const 转成 non-const。
dynamic_cast
安全向下转型,耗费重大运行成本。
当仅有一个指向base的指针或者引用,但是又当想子类来处理时。使用智能指针判断类型,使用类型安全容器存储。
std::vector<std::shared_ptr<Derived>> derived_ptrs;
for (auto iter = derived_ptrs.begin(); iter != derived_ptrs.end(); iter++) {
(*iter)->DerivedSpecialFunc();
}
reinterpret_cast
低级转型,不可移植。最常用的是转换“函数指针”。
typedef void (*FuncPtr)();
int doSomething();
reinterpret_cast<FuncPtr>(&doSomething);
static_cast
强行implicit转换。
- 1 转换成基类时,获得的时基类的副本。
virtual void Derived::func() {
static_cast<Base>(*this).func(); // wrong
}
上面子类函数中,想先调用基类的函数,再继续执行子类的函数。
但是,static_cast<Base>(*this)
获得的,是当前对象base class 成分的副本
。如果基类func函数中修改了成员变量,this并未改变,改变的只是副本,基类再读取的是未被改动的值。
virtual void Derived::func() {
Base::func(); // correct
}