OMG*_*chy 4 c++ type-conversion language-lawyer
请使用以下代码:
#include <iostream>
struct Base {
char x = 'b';
};
struct Derived : Base {
operator Base() { return Base { 'a' }; }
};
int main() {
Derived derived;
auto base = static_cast<Base>(derived);
std::cout << "BASE -> " << base.x << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
在g ++和clang ++下,这会产生:
BASE -> b
Run Code Online (Sandbox Code Playgroud)
我期待以下内容:
BASE -> a
Run Code Online (Sandbox Code Playgroud)
为什么?因为为什么我读这段代码,我看到里面的转换运算符Derived返回一个Base包含的实例'a'.
clang ++让我礼貌地发出警告:
main.cpp:9:5: warning: conversion function converting 'Derived' to its base class 'Base' will never be used
operator Base() { return Base { 'a' }; }
Run Code Online (Sandbox Code Playgroud)
研究这个警告,我发现这是设计的(为了清楚起见):
转换函数的类型([dcl.fct])是"不返回convert-type-id的参数的函数".转换函数永远不会用于将(可能是cv限定的)对象[...]转换为该类型(或对它的引用)的[可能是cv限定的]基类[...].
所以看起来两个编译器在这里做的都是正确的.我的问题是,为什么标准要求这种行为?
如果你可以在C++中覆盖转换为基类,那么你可以打破很多东西.例如,您究竟能够如何访问类的实际基类实例?您需要一些baseof模板,类似于std::addressof用于绕过il构想的operator&重载的模板.
允许这会产生关于代码含义的混淆.有了这个规则,很明显,在所有情况下,将类转换为其基类都会复制基类实例.
让我们有一个Animals的层次结构,我想编写一个函数,它接受一个可修改的Animal,而不需要切片。我该怎么做?我有两个选择:
void by_ref(Animal& );
void by_ptr(Animal* );
Run Code Online (Sandbox Code Playgroud)
我可以称之为:
Dog dog = ...;
by_ref(dog);
by_ptr(&dog);
Run Code Online (Sandbox Code Playgroud)
今天,这两个调用之间的唯一区别将是两个函数内部使用的语法,并且可能对nullptr. 这是因为DogtoAnimal&和Dog*toAnimal*是保证标准派生到基类的转换。没有替代。
但想象一下,如果我真的可以写:
struct Dog : Animal {
operator Animal&() { ... };
};
Run Code Online (Sandbox Code Playgroud)
现在这两个调用可以做完全不同的事情!我Dog*的Animal*转换仍然是同一条狗,但我Dog的Animal&转换是完全不同的Dog. 它甚至可以是一个Cat. 这将使所有这些代码基本上无法推理。
您需要一种特殊的机制来明确地为您提供特定类型的基本子对象:
by_ref(std::base<Animal>(dog));
Run Code Online (Sandbox Code Playgroud)
它基本上必须在任何地方使用,以保证任何依赖于继承的代码的正确性。这是很多代码。
有什么好处?如果你想要一个不同的基础子对象,你可以编写一个不同命名的函数:
struct Dog : Animal {
Animal& foo();
};
Run Code Online (Sandbox Code Playgroud)
命名可能是关于编程的两件难事之一,但最好只是想出你自己的名字而不是打开令人遗憾的袋子,这将允许人们编写他们自己的派生到基础但不是真的转换。
| 归档时间: |
|
| 查看次数: |
123 次 |
| 最近记录: |