[C++口试] explicit口试8问 —— 较难,可简单了解即可

打印 上一主题 下一主题

主题 1831|帖子 1831|积分 5493

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
Google C++规范建议所有单参数构造函数必须加explicit,除非明白需要隐式转换(如std::string从const char*构造)。
1. 隐式转换的实际危害

隐式转换可能导致资源泄漏或逻辑错误(如std::vector<int> v = 10;可能被误认为初始化10个元素,实际是分配10的容量)
  1. std::vector<int> v1 = 10;  // 编译错误!因为无法将int隐式转换为initializer_list
  2. std::vector<int> v2{10};   // 创建一个包含1个元素(值为10)的vector
  3. std::vector<int> v3(10);   // 创建包含10个元素(默认值0)的vector
复制代码
  若试图将int隐式转换为size_type(如size_t),且int为负数或超出size_t范围时,会触发窄化转换(narrowing conversion)错误
  1. int num = -10;
  2. std::vector<int> v(num);  // 编译错误:负数无法隐式转换为size_t
复制代码
明白构造函数调用​:


  • 利用vector(size_type)时,​始终用圆括号​:vector<int> v(10);
  • 需要初始化元素时,​显式利用列表​:vector<int> v{1, 2, 3};
2. explicit关键字的作用是什么?

修饰类的单参数构造函数(或多参数构造函数中仅有一个参数无默认值)
防止编译器进行隐式类型转换.
   explicit 关键字是 C++ 类型系统中提高代码安全性的一种机制。
  通过阻止隐式类型转换,它可以克制一些潜在的错误和不测行为。
  例如,防止将一个不相关的类型隐式转换为某个类的对象,从而导致逻辑错误。
  它使得代码的类型转换更加明白,提高了代码的可读性和可维护性,镌汰了因隐式转换带来的安全隐患。
  例子1:
  1. class MyClass {
  2. public:
  3.     explicit MyClass(int x) { /*...*/ }
  4. };
  5. // 必须显式调用构造函数
  6. MyClass obj(5);   // 合法
  7. MyClass obj = 5;  // 编译错误
复制代码
若未加explicit,以下代码合法但可能导致不测行为:
  1. MyClass obj = 5;  // 隐式调用构造函数
复制代码
例子2:
  1. class MyClass {
  2. public:
  3.     MyClass(int num) : value(num) {} // 没有使用 explicit 修饰的单参数构造函数
  4.     int getValue() const { return value; }
  5. private:
  6.     int value;
  7. };
  8. void printValue(const MyClass& obj) {
  9.     std::cout << obj.getValue() << std::endl;
  10. }
  11. int main() {
  12.     printValue(10); // 隐式类型转换
  13.     return 0;
  14. }
复制代码
假如加了explicit:
  1. printValue(MyClass(10));
复制代码
3. explicit能否用于多参数构造函数?​ 

当多参数构造函数中仅有一个参数无默认值时,explicit仍可生效。—— C++11
  1. class Point {
  2. public:
  3.     explicit Point(int x, int y = 0) : x_(x), y_(y) {}
  4. };
  5. Point p(3, 4);     // 合法
  6. Point p = {3, 4};  // 错误:禁止隐式转换
复制代码
下例,构造函数有两个参数且无默认值,但 explicit 仍可修饰,此时会阻止通过初始化列表(如 {1, 2})的隐式转换 
  1. #include <iostream>
  2. class MyClass {
  3. public:
  4.     // 多参数构造函数使用 explicit 修饰
  5.     explicit MyClass(int a, int b) : x(a), y(b) {}
  6.     void print() const {
  7.         std::cout << "x: " << x << ", y: " << y << std::endl;
  8.     }
  9. private:
  10.     int x;
  11.     int y;
  12. };
  13. void func(const MyClass& obj) {
  14.     obj.print();
  15. }
  16. int main() {
  17.     // 下面这行代码会编译错误,因为禁止了隐式转换
  18.     // func({1, 2});
  19.     // 显式类型转换
  20.     func(MyClass(1, 2));
  21.     return 0;
  22. }
复制代码
4. explicit如何与类型转换函数结合利用?

将explicit与类型转换函数结合利用,可以精准控制类型转换的显式性,克制隐式转换带来的潜在风险。
 案例1:
  1. class Fraction {
  2. public:
  3.     // 作用​:防止fh被意外转换为int或double,确保布尔判断的语义安全
  4.     explicit operator double() const { return value_; }
  5. };
  6. Fraction f;
  7. double d = f;       // 错误:需显式转换
  8. double d = static_cast<double>(f);  // 合法
复制代码
  explicit operator double() const { return value_; }​
  允许将类的对象显式地转换为 double 类型,但禁止隐式转换
  作用​:克制t + 5这类隐式算术操纵导致单位肴杂或逻辑错误 
案例2:对资源管理类(如智能指针、数据库连接),显式转换可阻止资源被隐式复制或开释
  1. class DatabaseConnection {
  2. public:
  3.     explicit operator bool() const { return isConnected(); }
  4.     explicit operator sql::Connection*() const { return rawPtr; }  // 显式获取原始指针
  5. };
  6. DatabaseConnection conn;
  7. if (conn) {
  8.     sql::Connection* raw = static_cast<sql::Connection*>(conn);  // 显式获取
  9. }
复制代码
explicit operator bool()在条件表达式(if/while)中会被隐式调用,这是C++尺度特例:
  1. FileHandle fh;
  2. if (fh) { ... }  // 合法:条件语句隐式调用explicit operator bool()
复制代码

5. 在模板编程中,explicit是否有特别注意事项?​ 

  1. template<typename T>
  2. class Wrapper {
  3. public:
  4.     explicit Wrapper(T value) : value_(value) {}
  5. };
复制代码
若模板参数T本身支持隐式转换,explicit会阻止外层类型的不安全转换。 
  1. Wrapper<int> w1 = 5;  // 错误:需显式构造
  2. Wrapper<int> w2(5);   // 合法
复制代码
隐式转换风险​:若 T 支持隐式转换(如 T 是 double,允许 int → double 的隐式转换),则 Wrapper<T> 的 explicit 构造函数会阻止从其他类型(如 int)直接隐式构造 Wrapper<T> 对象 
6. C++11对explicit的扩展有哪些?​ 

C++11允许explicit用于转换运算符(如operator bool),防止隐式转换为布尔值。
  1. class File {
  2. public:
  3.     explicit operator bool() const { return is_open_; }
  4. };
  5. File f;
  6. if (f) { ... }               // 合法:条件语句允许隐式调用 operator bool()
  7. bool flag = f;               // 错误:禁止隐式转换为 bool
  8. bool flag = static_cast<bool>(f);  // 合法
复制代码
尽管 explicit operator bool() 禁止隐式转换,但以下场景允许隐式调用:


  • 条件表达式​(如 if (f)、while (f))
  • 逻辑运算符​(如 !f、f && other)
  • 三元运算符​(如 f ? a : b)
 
7. explicit 关键字对拷贝构造函数和移动构造函数有影响吗?

当拷贝构造函数未标记为 explicit时:
  1. MyClass obj1(10);
  2. MyClass obj2 = obj1;   // 隐式调用拷贝构造函数
  3. printValue(obj1);      // 隐式创建临时对象并传递
复制代码
当拷贝构造函数标记为 explicit时:
  1. MyClass obj1(10);
  2. MyClass obj2 = obj1;   // 错误:禁止隐式调用拷贝构造函数
  3. printValue(obj1);       // 错误:禁止隐式创建临时对象
  4. printValue(MyClass(obj1));  // 显式调用拷贝构造函数创建临时对象
复制代码
会阻止隐式的拷贝或移动转换
  1. #include <iostream>
  2. class MyClass {
  3. public:
  4.     MyClass(int num) : value(num) {}
  5.     // 显式拷贝构造函数
  6.     explicit MyClass(const MyClass& other) : value(other.value) {}
  7.     int getValue() const { return value; }
  8. private:
  9.     int value;
  10. };
  11. void printValue(const MyClass& obj) {
  12.     std::cout << obj.getValue() << std::endl;
  13. }
  14. int main() {
  15.     MyClass obj1(10);
  16.     // 下面这行代码会编译错误,禁止隐式拷贝转换
  17.     // printValue(obj1);
  18.     // 显式拷贝
  19.     printValue(MyClass(obj1));
  20.     return 0;
  21. }
复制代码
场景​​未标记 explicit​​标记 explicit​拷贝构造(MyClass obj2 = obj1)隐式调用合法必须显式调用 MyClass(obj1)移动构造(MyClass obj2 = std::move(obj1))隐式调用合法必须显式调用 MyClass(std::move(obj1))函数传参(printValue(obj1))隐式创建临时对象必须显式创建临时对象  
8.如何筹划一个安全的单例类,克制隐式拷贝?​ 

要筹划一个安全的单例类并克制隐式拷贝,需要结合构造函数控制拷贝限制线程安全机制
  1. private:
  2.     explicit Singleton() {}  // 构造函数私有化
复制代码


  • 作用​:禁止外部通过 new 或直接构造创建实例。
  • 关键点​:explicit 确保无法隐式调用构造函数,进一步强化控制
  1. static Singleton& getInstance() {
  2.     static Singleton instance;  // C++11 保证线程安全
  3.     return instance;
  4. }
复制代码


  • 线程安全​:C++11 尺度规定局部静态变量的初始化是线程安全的,无需额外加锁 
  • 延迟加载​:首次调用 getInstance() 时才创建实例,克制资源浪费
  1. Singleton(const Singleton&) = delete;
  2. Singleton& operator=(const Singleton&) = delete;
复制代码


  • 作用​:通过 delete 关键字显式禁用拷贝构造函数和赋值运算符,防止通过拷贝创建新实例
  • 必要性​:即使单例的地址固定,拷贝仍可能破坏逻辑唯一性(例如浅拷贝指针导致资源泄漏)
  1. class Singleton {public:    static Singleton& getInstance() {        static Singleton instance;        return instance;    }    void doSomething() { /*...*/ }    // 禁止拷贝    Singleton(const Singleton&) = delete;    Singleton& operator=(const Singleton&) = delete;private:
  2.     explicit Singleton() {}  // 构造函数私有化};
复制代码
利用方式: 
  1. Singleton& s = Singleton::getInstance();  // 合法
  2. Singleton s2;  // 错误:构造函数私有
复制代码
  双重检查锁(DCLP)​
  1. if (instance == nullptr) {  // 第一次检查
  2.     std::lock_guard<std::mutex> lock(mutex);
  3.     if (instance == nullptr) {  // 第二次检查
  4.         instance = new Singleton();
  5.     }
  6. }
复制代码


  • 注意​:需利用 volatile 或 std::atomic 防止指令重排序

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

乌市泽哥

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表