【问题标题】:C++: Implicit call of constructor from another constructorC ++:从另一个构造函数隐式调用构造函数
【发布时间】:2017-12-20 15:07:46
【问题描述】:

在我正在处理的项目中,我很难理解隐式构造函数调用。
有两个接口:接口A和接口B。
然后,有两个实现类:ImplementA和ImplementB,它们是从各自的接口派生的。任何时候都没有共同的祖先——即使接口和类非常相似。
第三个类 Component 可用于初始化任何 Implement 类。
所以我们有这样的东西:

class InterfaceA 
{
public:
    InterfaceA(){}
    virtual ~InterfaceA(){}

    // Some functions
}

class InterfaceB 
{
public:
    InterfaceB(){}
    virtual ~InterfaceB(){}

    // Some functions
}

class ImplementA : public InterfaceA
{
public:
    ImplementA(Component& input);
    ImplementA(Component* input);
    ImplementA(const ImplementA& impl);
    ImplementA(const ImplementB& impl);
    ImplementA();
    ~ImplementA(void);

private:
    Component* m_component;
    bool some_boolean;
}

class ImplementB : public InterfaceB
{
public:
    ImplementB(Component& input);
    ImplementB(const ImplementA& impl);
    ImplementB(const ImplementB& impl);
    ImplementB();
    ~ImplementB(void);

private:
    Component* m_component;
}

然后,我们有一个函数返回一个指向组件的指针:

Component* foo()
{
    Component* result = new Component();
    ...
    return result;
}

最后,在代码的某处,我们得到以下返回:

return new ImplementB(foo());

运行时,上面的行执行 foo(),然后执行 ImplementA(Component* input),然后执行 ImplementB(const ImplementA& impl) - 这让我非常困惑。
1. 为什么它甚至可以编译?编译器不应该抱怨这种类型的参数没有有效的构造函数(当我在相关调用中更改某些内容时它会这样做)。顺便说一句,我正在使用 Visual Studio 2012。
2.我知道当构造函数获取一个指向对象的指针作为参数时,它首先调用参数的复制构造函数。所以,如果我忽略了没有合适的构造函数的事实 - 它不应该使用 Component 的复制构造函数而不是 ImplementA 吗? ImplementA 似乎与 ImplementB 完全没有联系(除了它们具有相似的结构)。

我错过了什么?在这种情况下,什么可能导致这种行为?

【问题讨论】:

  • 也许你应该让你的构造函数explicit 这样就不允许像这样的隐式转换?

标签: c++ oop visual-studio-2012 copy-constructor


【解决方案1】:

这就是你拥有太多令人困惑的构造函数所得到的结果。我也强烈怀疑你到处都有内存泄漏。

注意ImplementA 有一个构造函数ImplementA(Component&) 和一个构造函数ImplementA(Component*)。但是ImplementB 只有ImplementB(Component&)。这令人困惑。

所以当您执行new ImplementB(foo()) 时,没有构造函数可以直接接受Component*。编译器会寻找其他选项。具体来说,它正在寻找一种将参数Component* 转换为ImplementB 的某些构造函数接受的方法。

C++ 有一种叫做用户定义转换的东西。您可以定义转换运算符转换构造函数,它们定义了类型之间的新隐式类型转换。事情是这样的:任何具有单个参数的构造函数都是转换构造函数,除非它被标记为explicit。 (这可能是 C++ 中的一个设计错误。但我们坚持下去。)

因此,ImplementA(Component*) 定义了从Component*ImplementA 的隐式转换。还有 ImplementB(const ImplementA&) 构造函数可以接受 ImplementA 临时就好了。所以编译器使用这个构造函数,使用转换构造函数来创建临时的,最终的结果就是你看到的执行。

解决方案是让所有这些构造函数explicit,同时定义更少、更少混淆的构造函数。并使用智能指针来消除内存泄漏。

【讨论】:

  • 所以基本上,C++ 会做一个后翻来找到一种方法将参数转换为构造函数(构造函数之一)将接受的东西?它可以进行多少转换有限制吗?这是一些旧代码 - 我搞砸的原因之一是摆脱内存泄漏。我希望我可以重写它。
  • 它将只允许一次用户定义的转换。
  • @A.Pet - 具体来说,我们有 foo() return Component* -> ImplementA -> ImplementB。由于到ImplementB 的“转换”是显式的(c'tor 写在代码中),我们只有一个 隐式 转换,尽管我们确实有 两个转换。如果该行改为return foo(),则您将进行两次隐式转换,并且代码将无法编译。
【解决方案2】:
return new ImplementB(foo());

运行时,上面的行先执行foo(),然后执行ImplementA(Component* input),然后执行ImplementB(const ImplementA& impl)——这让我很困惑。

foo 的调用返回Component*。当我们检查 ImplementB 的构造函数时,我们发现

ImplementB(Component& input);
ImplementB(const ImplementA& impl);
ImplementB(const ImplementB& impl);

所以这些都不接受Component*

但“幸运的是”其中一个选项是 ImplementA可以从指针构造。

并且一个在传递参数时允许“自定义转换”。

【讨论】:

  • 只有一个?这是由标准设置还是依赖于某些东西?
  • @A.Pet - 是的,标准最多说一个。编译器不应该尝试找到从一种类型转换为另一种类型的大约 20 个步骤。这对程序员来说是完全混乱的,对编译器来说也是很多工作。
【解决方案3】:

foo 返回一个Component*

ImplementB 没有接受Component* 的构造函数。但是它有一个接受const ImplementA& 的构造函数。

现在Component* 可以通过接受Component* 的后者的构造函数隐式转换ImplementA。因此它被转换,生成的 ImplementA 临时通过 const 引用传递给 ImplementB 的适当构造函数。

如果您重视自己的理智,您可能不希望在代码附近的任何地方进行任何隐式转换。要停止用户定义类型的隐式转换,请声明所有单参数构造函数explicit

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-24
    • 1970-01-01
    • 1970-01-01
    • 2010-12-15
    • 2014-03-12
    • 2017-01-05
    • 1970-01-01
    • 2010-09-23
    相关资源
    最近更新 更多