首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Effective C++ 条款 24-29 深度解析:设计声明与实现篇

     編輯:關於C++

条款 24:若所有参数皆需类型转换,请采用 non-member 函数

问题场景

有理数类 Rational,重载 operator*。成员函数版本:

class Rational {
public:
    const Rational operator*(const Rational& rhs) const;
};
Rational oneHalf(1,2);
Rational result = oneHalf * 2;     // OK: oneHalf.operator*(2)
Rational result2 = 2 * oneHalf;    // Error: 2.operator*(oneHalf) 不存在

编译器尝试 operator*(2, oneHalf) 也失败,因无匹配非成员函数。

解决方案

// 非成员非友元函数(优先)
const Rational operator*(const Rational& lhs, const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.numerator(),
                    lhs.denominator() * rhs.denominator());
}

// 若需访问 private 成员,声明为 friend
class Rational {
    friend const Rational operator*(const Rational&, const Rational&);
    // ...
};

决策原则

  • 不需访问私有成员 → non-friend non-member(最大封装性)
  • 需访问私有成员 → friend non-member

条款 25:考虑写出不抛异常的 swap 函数

默认 swap 缺陷

pImpl 惯用法下,默认 swap 需拷贝整个实现对象,效率低且可能抛异常。

标准化 swap 实现模式

  1. Public 成员 swap:交换指针,noexcept
  2. 同名非成员 swap:调用成员 swap,置于类所在命名空间
  3. 特化 std::swap(仅类,非模板):调用成员 swap
  4. 调用时 using std::swap:启用 ADL 查找
class Widget {
    WidgetImpl* pImpl;
public:
    void swap(Widget& other) noexcept { // 1. 成员 swap
        using std::swap;
        swap(pImpl, other.pImpl);
    }
};

namespace WidgetStuff {
    template<typename T>
    void swap(Widget<T>& a, Widget<T>& b) { // 2. 非成员 swap
        a.swap(b);
    }
}

namespace std {
    template<> // 3. 全特化(仅限类)
    void swap<Widget>(Widget& a, Widget& b) {
        a.swap(b);
    }
}

// 4. 使用示范
void foo(Widget& a, Widget& b) {
    using std::swap;
    swap(a, b); // 依次查找:同名空间 → 全局 → std::swap 特化 → std::swap 通用
}

注意

  • 严禁向 std 命名空间添加新模板(仅允许全特化)
  • 指针/内置类型 swap 不抛异常,自定义类型需自行保证

条款 26:尽可能延后变量定义式

理由

  1. 可读性:变量在首次使用处定义,上下文清晰
  2. 性能:避免无用构造/析构(尤其异常路径、提前返回)
// 改进前:encrypted 可能未用就析构
string encryptPassword(const string& password) {
    string encrypted;
    if (password.length() < MinLen) throw logic_error("too short");
    // ...
    return encrypted;
}

// 改进后:仅在需要时构造
string encryptPassword(const string& password) {
    if (password.length() < MinLen) throw logic_error("too short");
    string encrypted(password); // 直接初始化
    // ...
    return encrypted;
}

循环中定义 vs 赋值

// 方式 A:循环外定义 + 赋值
Widget w;
for (int i=0; i<n; ++i) { w = makeWidget(i); ... }
// 1构造 + 1析构 + n赋值

// 方式 B:循环内定义
for (int i=0; i<n; ++i) { Widget w(makeWidget(i)); ... }
// n构造 + n析构

// 选择:赋值成本 < 构造+析构 选 A,否则选 B(可读性优先)

条款 27:尽量少做转型动作

四种 C++ 风格转型

操作符用途特点
const_cast移除 const/volatile唯一能做此事
dynamic_cast安全向下转型运行期开销大,RTTI
reinterpret_cast低级位模式重解释不可移植,极少用
static_cast隐式转换强制化无运行期开销

避免转型策略

  1. 单一继承:用派生类指针容器替代基类指针容器 + dynamic_cast
  2. 多重继承:虚函数多态替代 dynamic_cast 链
// 反面教材:dynamic_cast 连串
for (auto p : winPtrs) {
    if (auto psw = dynamic_cast<SpecialWindow*>(p.get())) psw->blink();
    else if (...) ...
}

// 正面教材:虚函数分发
class Window { public: virtual void blink() {} };
class SpecialWindow : public Window { void blink() override { ... } };
for (auto p : winPtrs) p->blink(); // 无转型,自动分发

最佳实践

  • 无法避免时,隐藏在函数内部
  • 优先用 C++ 风格转型(易搜索、分类明确、编译器诊断好)

条款 28:返回 handles 时保证 const 约束不低于成员函数

handles = 引用/指针/迭代器。const 成员函数返回非 const handle 会破坏常量正确性。

class Rectangle {
    shared_ptr<RectData> pData;
public:
    // 错误:const 函数返回非 const 引用,允许修改内部状态
    Point& upperLeft() const { return pData->ulhc; }
    // 正确:返回 const 引用
    const Point& upperLeft() const { return pData->ulhc; }
};

个人观点扩展

  • 非 const 成员函数返回非 const handle → 语义等同 public 成员变量
  • 内置类型首选 getX()/setX() 分离读写

条款 29:为「异常安全」而努力

三级保证

  1. 基本承诺:异常后对象保持有效状态,不泄漏资源
  2. 强烈保证:异常后程序状态回滚到调用前(事务语义)
  3. 不抛异常:绝不抛出异常(内置类型操作通常满足)

实现技术:Copy-and-Swap

void PrettyMenu::changeBack(istream& imgSrc) {
    Lock ml(&mutex);
    auto pNew = make_shared<PMImpl>(*pImpl); // 复制副本
    pNew->bgImage.reset(new Image(imgSrc));   // 修改副本(可能抛异常)
    ++pNew->imageChanges;
    swap(pImpl, pNew); // 原子交换,不抛异常
}

保证传递性

函数的异常安全级别 ≤ 其调用的所有函数中最弱的级别。若调用非异常安全代码,则无法提供任何保证。

强烈保证的局限

  • 额外时空开销(副本构造)
  • 非局部状态修改(数据库、全局变量)难以回滚
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved