C++11 中类的部分新特性
C++11 中类的部分新特性
右值引用以及部分特性已经在前面的文章讲过了,大家可以翻看。
1. 默认和删除的函数 (Defaulted and Deleted Functions)
在 C++98 中,如果你声明了任何构造函数,编译器就不会再为你生成默认的无参构造函数。有时你希望同时拥有自定义构造函数和编译器生成的默认版本,就需要手动编写一个空的默认构造函数,这既繁琐又不高效(因为编译器生成的可能更好)。
C++11 允许你显式地指示编译器生成函数的默认实现或完全禁止某个函数。
= default: 显式要求编译器生成该函数的默认版本。= delete: 显式禁止编译器生成该函数的特定版本,任何尝试使用该函数的操作都会导致编译错误。
代码示例:
class MyClass {
public:
// 1. 使用 default 显式指定使用编译器生成的默认构造函数
MyClass() = default;
// 2. 自定义一个构造函数
MyClass(int value) : data(value) {}
// 3. 使用 delete 禁止拷贝构造函数,使这个类不可拷贝
MyClass(const MyClass&) = delete;
// 4. 使用 delete 禁止某个参数类型的重载
void process(double num) { /* ... */ }
void process(char) = delete; // 禁止传入 char 类型
int data;
};
int main() {
MyClass obj1; // 正确,调用 default 构造函数
MyClass obj2(42); // 正确,调用自定义构造函数
// MyClass obj3 = obj2; // 错误!拷贝构造函数被禁用
obj1.process(3.14); // 正确
// obj1.process('a'); // 错误!该重载被禁用
return 0;
}
优点: 代码意图更清晰,控制更精确。可以轻松地创建不可拷贝、不可移动的类(如单例),或禁止不希望的隐式类型转换。
2. 委托构造函数 (Delegating Constructors)
在 C++98 中,如果一个类有多个构造函数,它们通常有共同的初始化代码,为了避免重复,你不得不将这些代码提取到一个私有的init()函数中,然后在各个构造函数中调用它。但这并非最佳实践,因为init()函数是在构造函数体内部调用,无法用于初始化常量成员或引用成员。
C++11 允许一个构造函数调用同一个类中的另一个构造函数。这被称为委托。
代码示例:
class Employee {
std::string name;
int id;
std::string department;
public:
// 目标构造函数 (Target Constructor)
Employee(const std::string& n, int i, const std::string& d)
: name(n), id(i), department(d) {
std::cout << "Target constructor called.\n";
}
// 委托构造函数:委托给目标构造函数
Employee(const std::string& n, int i) : Employee(n, i, "Unknown") {
std::cout << "Delegating constructor called.\n";
}
// 另一个委托构造函数
Employee() : Employee("Anonymous", 0) {
std::cout << "Default delegating constructor called.\n";
}
};
int main() {
Employee emp1; // 先调用 Employee("Anonymous", 0),
// 再委托给 Employee("Anonymous", 0, "Unknown")
Employee emp2("Bob", 101, "IT");
return 0;
}
执行流程: 当调用委托构造函数时,它会先执行它委托的那个构造函数的初始化列表和函数体,然后再执行自己的函数体。
优点: 避免了代码重复,提高了可维护性,并且能正确地初始化所有成员(包括const和引用)。
3. 继承构造函数 (Inheriting Constructors)
当一个派生类没有定义任何自己的构造函数时,它会自动继承基类的构造函数。但一旦你为派生类定义了任何构造函数,基类的构造函数就不会被自动继承了。在 C++98 中,如果你希望使用基类的构造函数,你必须在派生类中手动重新定义它们,这非常繁琐。
C++11 使用 using Base::Base; 来显式继承基类的所有构造函数(除默认构造函数、拷贝构造函数、移动构造函数外)。
代码示例:
class Base {
public:
Base() { std::cout << "Base()\n"; }
Base(int x) { std::cout << "Base(" << x << ")\n"; }
Base(int x, const std::string& y) { std::cout << "Base(" << x << ", " << y << ")\n"; }
};
class Derived : public Base {
public:
// 使用 using 继承 Base 的所有构造函数
using Base::Base; // 继承 Base(int), Base(int, const std::string&)
// 派生类可以添加自己的成员
double extra_data = 3.14;
};
int main() {
Derived d1; // 错误!Base() 不会被继承,因为 Derived 没有自定义构造函数?
// 实际上,如果派生类没有任何构造函数(包括通过using继承的),
// 编译器会为它生成一个默认构造函数,该函数调用基类的默认构造函数。
// 这里因为使用了 `using`,情况变得复杂。通常建议明确定义需要的构造函数。
Derived d2(10); // 正确!调用继承的 Base(int)
Derived d3(20, "Hello"); // 正确!调用继承的 Base(int, const std::string&)
return 0;
}
注意: 继承的构造函数只初始化基类部分,派生类中新引入的成员会被默认初始化(如上例中的extra_data被初始化为3.14)。
4. 显式重写控制 (override 和 final)
为了更安全地使用虚函数和继承。
override: 显式注明一个成员函数旨在重写基类的虚函数。如果该函数没有成功重写任何虚函数(比如函数签名拼写错误),编译器将报错。final: 用于类,表示该类不能被继承;用于虚函数,表示该虚函数在派生类中不能被重写。
代码示例:
class Base {
public:
virtual void func() const;
virtual void foo(int);
void non_virtual();
};
class Derived : public Base {
public:
virtual void func() const override; // 正确,成功重写
// virtual void func() override; // 错误!签名不匹配 (缺少 const)
virtual void foo(int) override; // 正确
// void non_virtual() override; // 错误!基类函数非虚
void bar() final; // 错误!final 只能用于虚函数
};
class Base2 {
public:
virtual void do_something() final; // 该函数不能再被重写
};
class Derived2 : public Base2 {
public:
// virtual void do_something(); // 错误!Base2::do_something 是 final 的
};
class NoInheritance final { }; // 该类是 final 的
// class Try : public NoInheritance { }; // 错误!不能继承 final 类
优点: 防止意外的错误(如拼写错误导致没有重写),明确表达设计意图,增强代码可读性和安全性。
5. 类内成员初始化 (In-class Member Initialization)
C++11 允许在类定义内部直接为非静态数据成员指定默认值。
代码示例:
class Student {
private:
std::string name = "Unknown";
int age = 0;
// 对于 static const 整型成员,仍然可以在类内初始化
static const int max_grade = 100;
public:
Student() = default; // 使用默认值初始化 name 和 age
Student(const std::string& n) : name(n) {} // age 仍用默认值 0 初始化
Student(const std::string& n, int a) : name(n), age(a) {}
};
优点: 简化了构造函数编写,避免了在多个构造函数中重复初始化成员,使默认值一目了然。
6. 友元模板 (Friend Templates)
在 C++98 中,让一个模板类的所有实例都成为另一个类的友元很麻烦。C++11 简化了语法。
代码示例:
template<typename T>
class Box; // 前向声明
class Treasure {
int value;
// 声明 Box<T> 的所有实例都是 Treasure 的友元
template<typename U>
friend class Box;
};
template<typename T>
class Box {
public:
void peek(const Treasure& t) {
std::cout << "Treasure's value: " << t.value << std::endl; // 正确,是友元
}
};
7. 对齐控制 (alignas 和 alignof)
C++11 提供了标准化的方式来查询和指定类型的对齐要求。
alignof(Type): 返回类型的对齐要求(字节数)。alignas(N)/alignas(Type): 指定变量或类的对齐要求。
代码示例:
// 确保这个结构体按 16 字节对齐,这对 SIMD 指令等场景很重要
struct alignas(16) Vec4 {
float x, y, z, w;
};
int main() {
std::cout << "Alignment of int: " << alignof(int) << std::endl;
std::cout << "Alignment of Vec4: " << alignof(Vec4) << std::endl; // 很可能是 16
alignas(double) char buffer[1024]; // 让 buffer 按 double 的要求对齐
return 0;
}
总结
C++11 对类的增强是革命性的:
default/delete提供了对特殊成员函数更精确的控制。- 委托和继承构造函数减少了构造函数中的代码重复。
- 右值引用和移动语义解决了长期以来的性能痛点,是 C++11 的核心成就。
override和final使虚函数和继承的使用更加安全可靠。- 类内初始化让代码更简洁直观。
更多推荐
所有评论(0)