4

考虑这个示例代码:

#include <iostream>

class base {
    public:
    base() {
        std::cout << "base constructed" << std::endl;
    }
    base(const base & source) {
        std::cout << "base copy-constructed" << std::endl;
    }
};

class derived : public base {
    public:
    derived() {
        std::cout << "derived constructed" << std::endl;
    }
    derived(const derived &) = delete;
    derived(const base & source) : base(source) {
        std::cout << "derived copy-constructed from base" << std::endl;
    }
};

int main() {
    derived a;
    base b(a);
    derived c(a);

    return 0;
}

为什么调用 tobase::base(const base &)可以,但调用 toderived::derived(const base &)不行?两者都期望一个基本引用,并且都给出了一个派生引用。我的理解是派生的'是'基础。

为什么编译器在提供对派生类型对象的引用时derived::derived(const derived &)使用没有问题时坚持使用?base::base(const base &)

4

3 回答 3

2
derived a; 
derived c(a);

您有明确deleted的复制构造函数。这意味着上面的第二行将无法编译。与base案例的不同之处在于,在基本案例中,没有base声明的构造函数接受 a derived&,因此应用了转换。

但是在derived有这样一个函数的情况,它被声明和删除。重载解析将找到两者derived(derived const &),并且derived(base const &)两者都被声明,并将选择第一个作为最佳匹配。然后它会发现它被删除并抱怨。

如果要将其他构造函数与derived对象一起使用,则必须显式强制转换:

derived c( static_cast<base&>(a) );

在这种情况下,最好的重载成为derived( base const& )并且代码将编译。

于 2012-10-14T01:10:11.060 回答
2

显然“删除”其中一项默认内容并没有实际删除它的效果。曾经引以为豪的默认复制构造函数留下了一些可怕的可怕遗迹,它们会弹出并用阴森森的声音告诉你“我是 deeeeaaaaaad! ”。

这对我来说并不是一个完全令人惊讶的事实,尽管在你问这个问题之前我并没有具体意识到这一点。我不能引用标准的相关部分(我确信相关部分没有提到食尸鬼,尽管它应该)。而且我也相当肯定,一旦你关注一些关于一些可怕案例的令人难以置信的复杂故事,如果不是这样,这种情况就会以一种可怕的方式运作,这是有某种原因的。

而且,对您来说不幸的是,如果周围有比转换为基类更好的匹配项,那么它将被使用。例如,在这段代码中:

#include <iostream>

class A {
};

class B {
 public:
   void foo(const A &) { ::std::cerr << "B::foo(const A &) called!\n"; }
   void foo(const B &) { ::std::cerr << "B::foo(const B &) called!\n"; }
};

int main()
{
    B b;
    A &ar = b;
    b.foo(b);
    b.foo(ar);
};

将导致此输出:

B::foo(const B &) called!
B::foo(const A &) called!

这正是您所期望的。编译器不会将这种情况视为模棱两可。而且,如果您将void foo(const B &)其设置为私有受保护成员,那么编译器仍然会优先匹配它,并告诉您您尝试访问具有访问说明符的内容,该说明符说您不能。

将某些内容设置为“删除”就像简单地使用比private更受限制的特殊访问说明符声明它。

于 2012-10-14T01:32:42.180 回答
0

我要继续说这是因为签名derived c(a);与您删除的函数的签名完全匹配。如果可能,C++ 将尽量避免强制转换/提升/转换,并且会更喜欢最接近的匹配。转换a为正确的类型 ( base) 应该修复它,以便它停止匹配已删除函数的签名并开始匹配其他构造函数的签名。

于 2012-10-14T01:09:58.050 回答