在 C++11 及其后续版本中,= default
用于显式地指定编译器生成默认的构造函数、析构函数或拷贝/移动操作符。它允许你在类定义中明确地表示你希望使用编译器生成的默认实现,而不是自己手动实现这些函数。
语法
class MyClass {
public:
MyClass() = default; // 默认构造函数
~MyClass() = default; // 默认析构函数
MyClass(const MyClass&) = default; // 拷贝构造函数
MyClass& operator=(const MyClass&) = default; // 拷贝赋值操作符
MyClass(MyClass&&) = default; // 移动构造函数
MyClass& operator=(MyClass&&) = default; // 移动赋值操作符
};
使用场景
-
显式要求编译器生成默认函数:当你希望编译器生成默认的函数实现时,可以使用
= default
。这在某些情况下是必须的,例如当你有其他的构造函数或析构函数时,编译器不会自动生成默认的构造函数或其他特殊成员函数。class MyClass { public: MyClass(int value) : value(value) {} // 自定义构造函数 MyClass() = default; // 显式要求编译器生成默认构造函数 private: int value; };
-
性能优化:有时编译器生成的默认实现比手动实现更高效,因为编译器可以直接生成内联代码。
-
确保特殊成员函数是
trivial
或constexpr
:使用= default
可以保证某些情况下生成的特殊成员函数是trivial
或constexpr
的。
示例
以下是一个简单的示例,展示了如何使用 = default
:
#include <iostream>
class MyClass {
public:
MyClass() = default; // 默认构造函数
~MyClass() = default; // 默认析构函数
MyClass(const MyClass&) = default; // 拷贝构造函数
MyClass& operator=(const MyClass&) = default; // 拷贝赋值操作符
MyClass(MyClass&&) = default; // 移动构造函数
MyClass& operator=(MyClass&&) = default; // 移动赋值操作符
void display() const {
std::cout << "MyClass instance" << std::endl;
}
};
int main() {
MyClass obj1; // 调用默认构造函数
MyClass obj2 = obj1; // 调用拷贝构造函数
MyClass obj3 = std::move(obj1); // 调用移动构造函数
obj2 = obj3; // 调用拷贝赋值操作符
obj3 = std::move(obj2); // 调用移动赋值操作符
obj1.display();
obj2.display();
obj3.display();
return 0;
}
在这个示例中,MyClass
使用 = default
显式要求编译器生成默认的构造函数、析构函数、拷贝构造函数、拷贝赋值操作符、移动构造函数和移动赋值操作符。
注意事项
- 如果你定义了一个类的某些特殊成员函数(如构造函数、析构函数、拷贝/移动操作符),编译器将不会自动生成其他的默认特殊成员函数。此时你可以使用
= default
显式地要求编译器生成它们。 -
= default
只能用于特殊成员函数(构造函数、析构函数、拷贝/移动构造函数和拷贝/移动赋值操作符)。 - 如果某个默认的特殊成员函数被标记为
= delete
,则不能使用= default
。
通过使用 = default
,你可以更清晰地表达你的意图,并且在某些情况下还能获得更好的性能和代码优化。
= delete
= delete
用于显式删除某个函数,表示该函数不可用。通常用于删除特殊成员函数,如拷贝构造函数、拷贝赋值操作符等,以防止对象被不正确地复制或赋值。
class MyClass {
public:
MyClass() = default; // 默认构造函数
MyClass(const MyClass&) = delete; // 删除拷贝构造函数
MyClass& operator=(const MyClass&) = delete; // 删除拷贝赋值操作符
};
int main() {
MyClass obj1;
// MyClass obj2 = obj1; // 错误:拷贝构造函数被删除
// MyClass obj3;
// obj3 = obj1; // 错误:拷贝赋值操作符被删除
return 0;
}
constexpr
constexpr
用于指定一个函数或变量可以在编译时求值。对于构造函数,constexpr
表示该构造函数可以用于生成常量表达式。
class MyClass {
public:
constexpr MyClass(int value) : value(value) {}
constexpr int getValue() const { return value; }
private:
int value;
};
int main() {
constexpr MyClass obj(42);
static_assert(obj.getValue() == 42, "Value should be 42");
return 0;
}
noexcept
noexcept
用于指定一个函数不会抛出异常。这在优化和异常安全性方面很有用。
class MyClass {
public:
MyClass() noexcept = default;
void doSomething() noexcept {
// 不会抛出异常的代码
}
};
int main() {
MyClass obj;
obj.doSomething();
return 0;
}
override
override
用于显式指定一个虚函数覆盖了基类中的虚函数。这样可以帮助编译器检查函数签名是否匹配,防止因为签名不匹配而意外地创建了一个新的虚函数。
class Base {
public:
virtual void doSomething() {}
};
class Derived : public Base {
public:
void doSomething() override {
// 覆盖基类中的虚函数
}
};
final
final
用于指定一个类不能被继承,或者一个虚函数不能被进一步覆盖。
class Base {
public:
virtual void doSomething() final {
// 这个虚函数不能被覆盖
}
};
class Derived final : public Base {
// 这个类不能被继承
};
explicit
explicit
用于防止构造函数或转换运算符在不需要时隐式转换。这有助于避免意外的类型转换。
class MyClass {
public:
explicit MyClass(int value) : value(value) {}
private:
int value;
};
int main() {
// MyClass obj = 42; // 错误:构造函数是 explicit 的
MyClass obj(42); // 正确
return 0;
}
总结
这些关键字和特性提供了更细粒度的控制和更强的表达能力,使得代码更加清晰、健壮和高效。通过合理使用这些关键字和特性,你可以编写出更易于维护和理解的代码。
网友评论