Effective C++ 读书笔记

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;
}
  1. 复制自己的成员变量
  2. 调用所有基类的复制函数

以独立语句将new对象放入智能指针

func1以下调用可能会出现内存泄露:

func1(std::shared_ptr<ObjA>(new ObjA), func2());

调用func1之前,编译器创建代码做三件事情,在执行顺序如下时:

  1. 执行 new ObjA
  2. 调用func2()
  3. 调用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 &param) : 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 &param) {
	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
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值