价值取向的向上的

Value-based upcast

本文关键字:      更新时间:2023-10-16

我试图找到一些关于在其层次结构中转换类值的信息,但我只能找到有关转换指针到类的有用信息。

#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::subirept::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.
相关文章:
  • 没有找到相关文章