C++中的转换构造函数是什么?它有什么作用?

62

我听说C++有一种叫做“转换构造函数”或“转换构造器”的东西。这是什么,有什么用?我在看到下面的代码时提到了它:

class MyClass
{
  public:
     int a, b;
     MyClass( int i ) {}
}

 int main()
{
    MyClass M = 1 ;
}

5
Jeopardy! 主持人:C++03和C++11标准的第12.3.1节描述了这种类型的构造函数。参赛者:什么是转换构造函数?Stackoverflow 主持人:回答必须以真正的问题形式给出。 - amdn
3个回答

76
在C++03和C++11中,转换构造函数的定义是不同的。在两种情况下,它必须是一个非explicit构造函数(否则它不会参与隐式转换),但在C++03中它还必须能够接受单个参数。也就是说:
struct foo
{
  foo(int x);              // 1
  foo(char* s, int x = 0); // 2
  foo(float f, int x);     // 3
  explicit foo(char x);    // 4
};

构造函数1和2都是C++03和C++11中的转换构造函数。构造函数3必须接受两个参数,只在C++11中是转换构造函数。最后一个构造函数4不是转换构造函数,因为它是explicit的。
C++03:§12.3.1
没有function-specifier explicit声明的构造函数,如果只带一个参数,则指定了从其第一个参数的类型到其类类型的转换。这样的构造函数称为转换构造函数。
C++11:§12.3.1
没有function-specifier explicit声明的构造函数,指定了从其参数类型到其类类型的转换。这样的构造函数称为转换构造函数。
为什么C++11中具有多个参数的构造函数被认为是转换构造函数?这是因为新标准为我们提供了使用braced-init-lists传递参数和返回值的一些方便的语法。考虑以下示例:
foo bar(foo f)
{
  return {1.0f, 5};
}

能够将返回值指定为 花括号初始化列表 被认为是一种转换。这使用了带有 floatint 参数的 foo 转换构造函数。此外,我们可以通过 bar({2.5f, 10}) 调用此函数。这也是一种转换。因为它们是转换,所以它们使用的构造函数应该是 转换构造函数
因此,需要注意的是,如果使得 foo 的构造函数带有 explicit 函数说明符,则上述代码将无法编译。只有当存在可用于完成工作的转换构造函数时,才能使用上述新语法。
  • C++11: §6.6.3:

    使用带有花括号初始化列表return语句通过从指定的初始化列表进行复制列表初始化(8.5.4)来初始化要从函数返回的对象或引用。

    §8.5:

    在参数传递中发生的初始化[...]称为复制初始化。

    §12.3.1:

    显式构造函数像非显式构造函数一样构造对象,但只在直接初始化语法(8.5)或显式使用转换(5.2.9、5.4)时才这样做。


24

通过转换构造函数隐式转换

让我们将问题中的示例变得更加复杂。

class MyClass
{
  public:
     int a, b;
     MyClass( int i ) {}
     MyClass( const char* n, int k = 0 ) {}
     MyClass( MyClass& obj ) {}
}

前两个构造函数是转换构造函数。第三个是复制构造函数,因此它也是另一个转换构造函数。

转换构造函数允许从参数类型隐式转换为构造函数类型。这里,第一个构造函数允许将int转换为MyClass类的对象。第二个构造函数允许将字符串转换为MyClass类的对象。而第三个构造函数则将MyClass类的对象转换为另一个MyClass类的对象!

要成为转换构造函数,构造函数必须具有单个参数(在第二个构造函数中,第二个参数有一个默认值),并且不能使用关键字explicit进行声明。

然后,在主函数中初始化可以如下所示:

int main()
{
    MyClass M = 1 ;
    // which is an alternative to
    MyClass M = MyClass(1) ;

    MyClass M = "super" ;
    // which is an alternative to
    MyClass M = MyClass("super", 0) ;
    // or
    MyClass M = MyClass("super") ;
}

显式关键字和构造函数

那么,如果我们使用了 explicit 关键字呢?

class MyClass
{
  public:
     int a, b;
     explicit MyClass( int i ) {}
}

然后,编译器将不接受。
   int main()
    {
        MyClass M = 1 ;
    }

由于这是隐式转换,因此必须编写

   int main()
    {
        MyClass M(1) ;
        MyClass M = MyClass(1) ;
        MyClass* M = new MyClass(1) ;
        MyClass M = (MyClass)1;
        MyClass M = static_cast<MyClass>(1);
    }

explicit关键字总是用于防止构造函数的隐式转换,并且它适用于类声明中的构造函数。


第一个示例中的第三个构造函数不是复制构造函数。复制构造函数的参数必须是以下之一:X&const X&volatile X&const volatile X& - Joseph Mansfield
在最后一个示例中,您可以只写MyClass M(1);等。注意那些多字符字面量。 - chris
2
我认为构造函数不一定需要有一个参数才能成为转换构造函数。它只需要是非显式的即可:“声明没有函数说明符explicit的构造函数指定了从其参数类型到其类类型的转换。这样的构造函数称为转换构造函数。” - Joseph Mansfield
可以使用一个参数调用的构造函数,将传递的参数类型转换为类类型。这样的构造函数被称为转换构造函数。该内容来源于http://msdn.microsoft.com/en-us/library/s2ff0fz8(v=vs.80).aspx。与 C++03 / C++11 的差异也没问题。 - kiriloff
1
@MooingDuck 这句话的意思是“可以使用单个参数进行调用”- 基本上是同样的意思。 - Joseph Mansfield
显示剩余3条评论

4

转换构造函数是一种单参数构造函数,它在没有显式函数说明符的情况下声明。编译器使用转换构造函数将对象从第一个参数的类型转换为转换构造函数所属的类的类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接