C++ explicit关键字详解

简介:

单参数构造函数


单参数构造函数作为隐含的类型转换符号


C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有缺省值的多参构造函数),承担了两个角色。

  • 用于构建单参数的类对象
  • 隐含的类型转换操作符.

例如一个类A的构造函数A(int i)就是,既可以用来作为构造器,又可以实现隐式转换A a=1;因为1可以通过构造函数A(int i)转换为一个类A的对象。

#include <iostream>

class Base
{
public :
    Base(const char data)
    {
        std::cout <<"constructor..." <<std::endl;
        this->m_data = data;
    }

protected :

    char m_data;
};



int main(void)
{
    Base base1('a');        // 用于构建单参数的类对象
    Base base2 = 'b';     // 隐含的类型转换操作符

    return 0;
}

这种方法看起来很方便,但是有时这并不是我们想要的,但是由此可能引入一些其他问题。

由此带来的问题


比如如下的代码就可以正常运行

Base base = 10000;  //   由于10000可以被转成char类型

因此C++提供关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换发生.
特别对于如下代码

#include <iostream>


#include <cstring>


class Base
{
public :
    Base(const char *str)
    {
        std::cout <<"constructor..." <<std::endl;

        this->m_length = strlen(str);
        this->m_cstr = new char[this->m_length + 1];

        strcpy(this->m_cstr, str);
    }

    Base(const Base &base)
    {
        this->m_length = base.m_length;
        this->m_cstr = new char[this->m_length + 1];

        strcpy(this->m_cstr, base.m_cstr);
    }

    ~Base()
    {
        delete this->m_cstr;
    }

protected :

    char *m_cstr;
    int  m_length;
};



int main(void)
{
    Base base = (char *)10000;     // 隐含的类型转换操作符

    return 0;
}

这里写图片描述
声明为explicit的构造函数不能在隐式转换中使用,只能显示调用,去构造一个类对象。

这里写图片描述

拷贝构造函数copy construct


Copy constructor也是同样的,如果Copy constructor被声明为explicit,则这个类对象不能用于传参和函数返回值。但是仍然可以直接调用。

拷贝构造函数的调用时机


我们都知道,在C++中,拷贝构造函数的调用时机有如下三种情况

  1. 当用类的一个对象去初始化该类的另一个对象时
  2. 如果函数的形参是类的对象,调用函数,进行形参和实参结合时
  3. 如果函数的返回值是类的对象,函数执行完成返回调用者时
#include <iostream>

class Base
{
public :
    Base( )
    {
        //std::cout <<"default constructor..." <<std::endl;
        this->m_data = '\0';
    }

    Base(char data)
    {
        //std::cout <<"simple constructor..." <<std::endl;
        this->m_data = data;
    }

    Base(const Base &base)
    {
        std::cout <<"copy constructor..." <<std::endl;
        this->m_data = base.m_data;
    }

protected :

    char m_data;
};


//  参数为Base对象
void Func(const Base base)
{
}

//  返回值为base对象
Base Func( )
{
    Base base;

    return base;
}

void Copy()
{
    Base b1;
    Base b2 = b1;

}

int main(void)
{
    Base base(10);

    std::cout  <<std::endl <<"参数为Base对象..." <<std::endl;
    Func(base);     //  优化未优化情况下均调用1次拷贝构造函数

    std::cout  <<std::endl <<"返回值为base对象[可被优化]..." <<std::endl;
    Func();     //  优化情况下调用0次, 未优化情况下调用1次[函数返回时调用]

    Base b = Func();     //  优化情况下调用0次, 未优化情况下调用2次[函数返回时调用 + 初始化类型b时调用]


    std::cout  <<std::endl <<"用base对象初始化另外一个对象..." <<std::endl;
    Copy();     //  优化未优化情况下均调用1次拷贝构造函数

    return 0;
}

但是其实很多现代编译器的返回值优化技巧,返回值时的情况会被优化掉,但是这个并不是我们今天讨论的重点。

具体可参见

RVO-编译器返回值优化

在使用GNU/g++编译器时可以使用”-fno-elide-constructors”选项来强制g++总是调用copy构造函数,即使在用临时对象初始化另一个同类型对象的时候(主要是返回值为)

explicit对拷贝构造函数也会限制作用,将会阻止隐式拷贝构造函数的调用。
explicit对拷贝构造函数也会限制作用,将会阻隐式拷贝构造函数的调用.

将拷贝构造函数声明为explicit,则会阻止隐式拷贝构造函数的调用。

explicit关键字对拷贝构造函数的限制


这时候我们可以将参数传递或者函数返回修改为引用的方法,而初始化的时候采用显式调用拷贝构造函数。

#include <iostream>

class Base
{
public :
    Base( )
    {
        //std::cout <<"default constructor..." <<std::endl;
        this->m_data = '\0';
    }

    explicit Base(char data)
    {
        //std::cout <<"simple constructor..." <<std::endl;
        this->m_data = data;
    }

    explicit Base(const Base &base)
    {
        std::cout <<"copy constructor..." <<std::endl;
        this->m_data = base.m_data;
    }

protected :

    char m_data;
};


//  参数为Base对象
void Func(const Base& base)     //  传入引用对象
{
}

//  返回值为base对象
Base& Func( )
{
    Base base;

    return base;            //  返回引用对象
}

void Copy()
{
    Base b1;
    Base b2(b1);            //  Base b2 = b1显示调用拷贝构造函数

}

int main(void)
{
    Base base(10);

    std::cout  <<std::endl <<"参数为Base对象..." <<std::endl;
    Func(base);     //  优化未优化情况下均调用1次拷贝构造函数

    std::cout  <<std::endl <<"返回值为base对象[可被优化]..." <<std::endl;
    Func();     //  优化情况下调用0次, 未优化情况下调用1次[函数返回时调用]

    //Base b(Func());     返回局部对象


    std::cout  <<std::endl <<"用base对象初始化另外一个对象..." <<std::endl;
    Copy();     //  优化未优化情况下均调用1次拷贝构造函数

    return 0;
}

总结


C++中,一个参数的构造函数(或者除了第一个参数外其余参数都有缺省值的多参构造函数),承担了两个角色。

  • 用于构建单参数的类对象
  • 隐含的类型转换操作符

声明为explicit的构造函数不能在隐式转换中使用,只能显示调用,去构造一个类对象。

explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了

但是将拷贝构造函数声明成explicit并不是良好的设计,一般只将有单个参数的constructor声明为explicit,而copy constructor不要声明为explicit.



转载:http://blog.csdn.net/gatieme/article/details/50867575


目录
相关文章
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
600 0
|
安全 编译器 程序员
C++ noexcept 关键字的关键作用
`noexcept` 关键字在 C++ 中扮演着重要角色,通过正确使用 `noexcept`,可以提升程序的性能、增强代码的可读性和安全性,并且有助于编译器进行优化。在编写 C++ 代码时,应仔细考虑每个函数是否应该声明为 `noexcept`,以充分利用这一特性带来的优势。通过本文的介绍,希望开发者能够更好地理解和应用 `noexcept` 关键字,从而编写出更加高效、健壮的 C++ 程序。
607 8
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
380 1
|
存储 安全 编译器
C++入门 | auto关键字、范围for、指针空值nullptr
C++入门 | auto关键字、范围for、指针空值nullptr
545 4
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
存储 安全 编译器
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
【C++航海王:追寻罗杰的编程之路】引用、内联、auto关键字、基于范围的for、指针空值nullptr
355 5
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
320 0
|
存储 编译器 程序员
C++一分钟之-auto关键字与类型推导
【6月更文挑战第21天】`auto`在C++11中重生,简化了类型声明,尤其在处理复杂类型时。它让编译器根据初始化值推导变量类型,减少了冗余和错误。使用`auto`简化了迭代器声明和函数返回类型推导,但也带来挑战:类型推导可能不直观,未初始化的`auto`是错误的,且过度使用影响可读性。使用`auto&`和`auto*`明确引用和指针,`decltype`辅助复杂类型推导,保持适度使用以维持代码清晰。
336 1