价值取向的向上的
Value-based upcast
我试图找到一些关于在其层次结构中转换类值的信息,但我只能找到有关转换指针到类的有用信息。
#include <map>
#include <string>
#include <iostream>
class Base {
protected:
std::map<std::string, std::string> properties;
};
class Sub: public Base {
public:
std::string &first_name() {
return properties["first_name"];
}
std::string &last_name() {
return properties["last_name"];
}
};
Base factory() {
Sub sub;
sub.first_name() = "John";
sub.last_name() = "Doe";
return sub;
}
int main() {
Base base(factory());
Sub sub(static_cast<const Sub &>(base));
std::cout << "First name: " << sub.first_name() << std::endl;
std::cout << "Last name: " << sub.last_name() << std::endl;
return 0;
}
上面程序的行为是有问题的还是定义良好的?我基本上是处理基类的子类,其中只有基类具有属性。所有子类都只有函数。如果物体可以自由地从基座转换到潜艇,然后再转换回来,这有问题吗?
您的factory
函数按值返回,因此Sub
在返回中被切片,将返回类型更改为Base
。然后,当您向下转换为Sub
时,您就有了未定义的行为(因为您不能将对象强制转换为非在任何情况下的类型),并且所有的赌注都取消了。
如果你实际上有一个Sub
对象,它暂时被当作Base
指针或引用,那么将它强制转换回Sub
是完全合法的。在这种情况下,只要factory
函数返回,您就不再拥有子类对象,因为派生部分已经被切掉了。
Base factory() {
Sub sub;
return sub;
}
函数的第一行构造了一个Sub
的实例。第二行创建了一个完全独立的Base
实例,它是第一行Base
部分的副本。原来的Sub
实例在函数返回之前被销毁。返回的对象是通过复制Sub
构造的这一事实不会被保留,即使保留也不会有用。返回的对象只是一个Base
,而不是Sub
。将其强制转换为Sub&
是未定义的,原因与将float
强制转换为double&
是未定义的(尽管static_cast
不允许您执行后者)。
这是值语义,经常引起来自Java和c#等语言的程序员的困惑。如果你想要在那些语言中使用的引用语义,你必须通过使用引用或智能指针来请求它。
如果您确实想要从factory()
返回的Base
实例构造 Sub
的本地实例("基于值的向下转换"的唯一合理含义),如果您在Sub
中添加一个额外的构造函数,这很容易:
class Sub : public Base {
public:
Sub(const Base &base) : Base(base) {}
//...
};
Base factory()
{
Base base;
return base;
}
int main() {
Sub sub(factory());
std::cout << sub.first_name() << " " << sub.last_name() << "n";
}
您可能会发现在这里使用组合而不是继承是更好的设计。
使用问题中提到的限制,这实际上是定义的行为。这是静态分析器CBMC的c++代码中使用的模式,例如:https://github.com/diffblue/cbmc/blob/develop/src/util/irep.h
代码库中的大多数实体类继承自irept
。它们不允许引入实例成员变量,只允许引入成员函数。所有数据应存储在irept::sub
和irept::named_sub
中。只要子类没有引入实例成员变量,在返回时所提供的示例代码中就不会发生实际的切片。
/// To simplify this process, there are a variety of classes that inherit
/// from ref irept, roughly corresponding to the ids listed (i.e. `ID_or`
/// (the string "or”) corresponds to the class ref or_exprt). These give
/// semantically relevant accessor functions for the data; effectively
/// different APIs for the same underlying data structure. None of these
/// classes add fields (only methods) and so static casting can be used. The
/// inheritance graph of the subclasses of ref irept is a useful starting
/// point for working out how to manipulate data.
- 没有找到相关文章