第4章:设计与声明(Designs and Declarations)
一、导读
1.1 本章学习目标
第4章讲解如何设计良好的接口和声明。Meyers 在此给出了关于接口设计、类型转换、参数传递等方面的重要建议。
本章核心学习目标:
- 掌握接口设计的原则:使接口容易正确使用、难以错误使用
- 理解 explicit 的作用:防止隐式类型转换
- 掌握传值 vs 传引用的选择:什么时候用哪种?
- 理解返回引用的场景和陷阱
1.2 为什么本章如此重要
好的接口设计能防止错误的发生,比在代码中检查错误更有效。
1.3 核心问题引导
为什么单参数构造函数应该用 explicit 修饰?
传 const 引用比传值有什么优势?
为什么不应该返回局部变量的引用?
二、核心概念详解
2.1 使接口容易正确使用
cpp
// 不好: 容易误用的接口
class Date {
public:
Date(int month, int day, int year);
};
Date d(3, 30, 2024); // 是 3月30日 还是 3日30月?
// 好: 使用强类型防止误用
struct Month {
explicit Month(int m) : value(m) {}
int value;
};
struct Day { explicit Day(int d) : value(d) {} int value; };
struct Year { explicit Year(int y) : value(y) {} int value; };
class SafeDate {
public:
SafeDate(const Month& m, const Day& d, const Year& y);
};
SafeDate d(Month(3), Day(30), Year(2024)); // 清晰无误2.2 explicit 防止隐式转换
cpp
class Name {
std::string name;
public:
explicit Name(std::string n) : name(std::move(n)) {}
// 不加 explicit: string → Name 的隐式转换
};
void printName(const Name& n);
// 有 explicit:
// printName("hello"); // 编译错误
printName(Name("hello")); // OK: 显式转换
// 不加 explicit:
// printName("hello"); // OK: 隐式转换 string → Name
// 可能导致意外行为2.3 传值 vs 传 const 引用
cpp
// 传值: 适用于小对象(int, double, 指针等)
void setCount(int count); // OK
// 传 const 引用: 适用于大对象(string, vector, 自定义类等)
void setName(const std::string& name); // OK: 避免拷贝
void process(const std::vector<int>& data); // OK
// 不要返回 const 引用指向局部变量
const std::string& getGreeting() {
std::string greeting = "Hello";
return greeting; // 错误!返回局部变量的引用
}
// 正确: 返回值(利用移动语义或 NRVO)
std::string getGreetingSafe() {
std::string greeting = "Hello";
return greeting; // OK: 移动或 NRVO
}三、实战案例
3.1 案例一:接口设计实践
cpp
// 好的接口设计示例
class Connection {
std::string host;
int port;
std::chrono::milliseconds timeout;
Connection(std::string h, int p, std::chrono::milliseconds t)
: host(std::move(h)), port(p), timeout(t) {}
public:
// 使用 Builder 模式防止参数混淆
class Builder {
std::string host = "localhost";
int port = 8080;
std::chrono::milliseconds timeout{5000};
public:
Builder& setHost(std::string h) { host = std::move(h); return *this; }
Builder& setPort(int p) { port = p; return *this; }
Builder& setTimeout(std::chrono::milliseconds t) { timeout = t; return *this; }
Connection build() { return Connection(host, port, timeout); }
};
static Builder builder() { return Builder{}; }
};
// 使用:
auto conn = Connection::builder()
.setHost("db.example.com")
.setPort(5432)
.setTimeout(std::chrono::seconds(10))
.build();四、本章小结
接口设计应使正确使用容易、错误使用困难
explicit 防止意外的隐式类型转换
传 const 引用避免大对象拷贝,传值适用于小对象
不要返回局部变量的引用
五、思考与练习
思考题
为什么 std::string 的参数通常传 const 引用?
什么时候应该返回值而非引用?
编程练习
设计一个 Builder 模式的数据库配置类。
实现一个类型安全的日期类,防止月/日/年参数混淆。