考虑这个示例代码:
#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;
}
为什么拨打base::base(const base &)
是正常的,但是对derived::derived(const base &)
的呼叫不是?两者都期望一个基本参考,并且都给出了一个派生参考。我的理解是派生'是'基础。
为什么编译器在提供对类型派生对象的引用时使用derived::derived(const derived &)
时没有问题时,仍坚持使用base::base(const base &)
?
答案 0 :(得分:2)
derived a;
derived c(a);
您已明确deleted
复制构造函数。这意味着上面的第二行将无法编译。与base
情况的区别在于,在基本情况下,没有声明base
的构造函数需要derived&
,因此会应用转换。
但是在derived
的情况下是这样的函数,它被声明并删除。重载决策将同时找到derived(derived const &)
和derived(base const &)
两者,并将选择第一个作为最佳匹配。然后它会发现它被删除并抱怨。
如果要将其他构造函数与derived
对象一起使用,则必须显式转换:
derived c( static_cast<base&>(a) );
在这种情况下,最佳重载变为derived( base const& )
,代码将编译。
答案 1 :(得分: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 更受限制的特殊访问说明符来声明它。
答案 2 :(得分:0)
我要继续说这是因为derived c(a);
的签名与您删除的功能的签名完全匹配。如果可能的话,C ++会尝试避免演员表/促销/转换,并且会更喜欢最接近的匹配。将a
强制转换为正确的类型(base
)应该修复它,以便它停止匹配已删除函数的签名并开始匹配其他构造函数的签名。