C++ EXPLICIT 关键词

黎 浩然/ 19 5 月, 2022/ C/C++, 计算机/COMPUTER/ 0 comments

了解explicit的作用前,我们先来回顾以下C++的构造函数和赋值函数重载

构造函数与赋值运算符重载

默认构造函数 & 带参构造函数

class Box {
public:
    // Default constructor
    Box() {}
    // Initialize a Box with equal dimensions (i.e. a cube)
    explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
    {}
    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}
    int Volume() { return m_width * m_length * m_height; }
private:
    // Will have value of 0 when default constructor is called.
    // If we didn't zero-init here, default constructor would
    // leave them uninitialized with garbage values.
    int m_width{ 0 };
    int m_length{ 0 };
    int m_height{ 0 };
};
int main() {
    Box b; // Calls Box()
    // Using uniform initialization (preferred):
    Box b2 {5}; // Calls Box(int)
    Box b3 {5, 8, 12}; // Calls Box(int, int, int)
    // Using function-style notation:
    Box b4(2, 4, 6); // Calls Box(int, int, int)
}
  • 在一个新的实例刚出来的时候,调用的就是这种类型的构造函数!(即使使用了“=”符号)
  • 默认构造函数是特殊成员函数。 未在类中声明任何构造函数,编译器将提供隐式默认 inline 构造函数
#include <iostream>
using namespace std;

class Box {
public:
    int Volume() {return m_width * m_height * m_length;}
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
};

int main() {
    Box box1; // Invoke compiler-generated constructor
    cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}
  • 如果依赖于隐式默认构造函数,请确保初始化类定义中的成员,如前面的示例所示。
  • 如果声明了任何非默认构造函数,编译器不会再提供隐式默认构造函数

拷贝构造函数 & 拷贝赋值函数

如果类成员都是简单类型(如标量值),则编译器生成的复制构造函数已足够,无需定义自己 的类型。 如果类需要更复杂的初始化,则需要实现自定义复制构造函数。 例如,如果类成员 是指针,则需要定义复制构造函数以分配新内存,并复制另一个指向的对象中的值。 编译器生 成的复制构造函数只复制指针,以便新指针仍指向另一个指针的内存位置。复制构造函数可能 具有以下签名之一:

Box(Box& other); // Avoid if possible--allows modification of other.
Box(const Box& other);
Box(volatile Box& other);
Box(volatile const Box& other);
// Additional parameters OK if they have default values
Box(Box& other, int i = 42, string label = "Box");

WHEN?

  • 当在一个新的实例刚出来的时候,并且参数为同类实例的时候(即使使用了“=”符号)
  • 当函数的参数是非引用非指针的类类型时(一次实例拷贝)
  • 当函数的返回值是非引用非指针的类类型时(未优化会有一次到两次实例拷贝)
#include <iostream>
using namespace std;

class Box {
public:
    Box() {
      id = num++;
    }
    Box(const Box& other) {
        id = num++;
        cout << "Copying Box " << id << "!" << endl;
    }
    ~Box() {
        cout << "Destorying Box " << id  << "!" <<endl;
    }
    int Volume() {return m_width * m_height * m_length;}
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
    int id;
    static int num;
};
int Box::num = 0;

Box returnABox() { // 以下注释为第一次调用此函数的行为 
    Box box; // 默认构造id==0的box,因此无有输出 
    return box; // 复制构造id==1的box,用以返回
} // 返回拷贝后,析构id==0的box

int main() {
    Box box = returnABox(); //复制构造id==2的box对象
    //返回的id==1的box(在”=“后)被析构 
    //析构id==2的box
}
//g++ -O0 -fno-elide-constructors test.cpp -o test
//Copying Box 1!
//Destorying Box 0!
//Copying Box 2!
//Destorying Box 1!
//Destorying Box 2!

如果打开返回值优化,那么上面的例子将不会调用拷贝构造函数!

使用赋值运算符“=”并且未产生新实例的情况下,一般调用拷贝赋值函数:

#include <iostream>
using namespace std;

class Box {
public:
    Box() {
        id = num++;
    }
    Box(const Box& other) {
        id = num++;
        cout << "Copying Box " << id << "!" << endl;
    }
    Box &operator=(const Box &box) {
        id = box.id;
        cout << "Copy assignment function of " << id << "!" << endl;
    }
    ~Box() {
        cout << "Destorying Box " << id  << "!" <<endl;
    }
    int Volume() {
        return m_width * m_height * m_length;
    }
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
    int id;
    static int num;
};
int Box::num = 0;

Box returnABox() {      // 以下注释为第一次调用此函数的行为
    Box box;            // 默认构造id==1的box,因此无有输出
    return box;         // 复制构造id==2的box,用以返回
}                       // 返回拷贝后,析构id==1的box

int main() {       
    Box box;            // 默认构造id==0的box,因此无有输出 
    box = returnABox(); // 拷贝赋值id==2的box
                        //返回的id==2的box(在”=“后)被析构
}                       // 返回拷贝后,析构id==1的box
//Copying Box 2!
//Destorying Box 1!
//Copy assignment function of 2!
//Destorying Box 2!
//Destorying Box 2!
  • 使用赋值运算符并且实例不是在第一次初始化的时候拷贝赋值函数被调用
  • main结束后,悬浮(无法通过代码访问)的对象的内存区域无法被自动析构

EXP

#include <iostream>
using namespace std;

struct A {
A(int) { }
    operator bool() const { return true; }
};

struct B {
    explicit B(int) {}
    explicit operator bool() const { return true; }
};

void doA(A a) {}

void doB(B b) {}

int main() {
    A a1(1); // OK:直接初始化
    A a2 = 1; // OK:复制初始化
    A a3{ 1 }; // OK:直接列表初始化
    A a4 = { 1 }; // OK:复制列表初始化
    A a5 = (A)1; // OK:允许 static_cast 的显式转换初始化 
    // 以上这些初始化都会调用构造函数
    // 是否调用拷贝构造函数取决于参数
    doA(1); // OK:允许从 int 到 A 的隐式转换
    if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换 
    bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
    bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
    bool a8 = static_cast<bool>(a1); // OK :static_cast 进行隐式转换
    B b1(1); // OK:直接初始化
    //B b2 = 1; // NO:被 explicit 修饰构造函数的对象不可以复制初始化
    B b3{ 1 }; // OK:直接列表初始化
    //B b4 = { 1 }; // NO:被 explicit 修饰构造函数的对象不可以复制列表初始化
    B b5 = (B)1; // OK:允许 static_cast 的显式转换初始化
    //doB(1); // NO:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
    if (b1);// OK:被explicit修饰转换函数B::operator bool()的对象可以从B到 bool的按语境转换
    bool b6(b1); // OK:被explicit修饰转换函数B::operator bool()可以从B到 bool的按语境转换
    //bool b7 = b1; // NO:被 explicit 修饰转换函数 B::operator bool()的对象不可以隐式转换
    bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化 
    return 0;
}

这些东西大概就是需要记下来的,I mean,写下来,不必记在脑子里!

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*