C++中指针的自动返回类型

Automatic Return Type for Pointers in C++

本文关键字:返回类型 指针 C++      更新时间:2023-10-16

我希望标题不要太混乱。我所拥有的是一个类StorageManager,它包含从Storage派生的类的对象列表。下面是一个例子。

struct Storage {};                         // abstract
class StorageManager
{
private:
map<string, unique_ptr<Storage>> List; // store all types of storage
public:
template <typename T>
void Add(string Name)                  // add new storage with name
{
List.insert(make_pair(Name, unique_ptr<Storage>(new T())));
}
Storage* Get(string Name)              // get storage by name
{
return List[Name].get();
}
};

假设Position是一种特殊的存储类型。

struct Position : public Storage
{
int X;
int Y;
};

由于我最后一个问题的答案很好,Add函数已经工作了。我想改进的是Get函数。它合理地返回一个指针Storage*,我可以像下面这样使用它。

int main()
{
StorageManager Manager;
Manager.Add<Position>("pos");    // add a new storage of type position
auto Strge = Manager.Get("pos"); // get pointer to base class storage
auto Pstn = (Position*)Strge;    // convert pointer to derived class position
Pstn->X = 5;
Pstn->Y = 42;
}

有没有一种方法可以通过自动返回指向派生类的指针来消除这种指针强制转换?也许使用模板?

使用:

template< class T >
T* Get(std::string const& name)
{
auto i = List.find(name);
return i == List.end() ? nullptr : static_cast<T*>(i->second.get());
}

然后在你的代码中:

Position* p = Manager.Get<Position>("pos");

除了@BigBoss已经指出的之外,我看不出你能为Get成员函数做些什么,但你可以改进Add成员以返回使用过的存储。

template <typename T>
T* Add(string Name)                  // add new storage with name
{
T* t = new T();
List.insert(make_pair(Name, unique_ptr<Storage>(t)));
return t;
}
// create the pointer directly in a unique_ptr
template <typename T>
T* Add(string Name)                  // add new storage with name
{
std::unique_ptr<T> x{new T{}};
T* t = x.get();
List.insert(make_pair(Name, std::move(x)));
return t;
}

EDIT临时阻止我们使用dynamic_castEDIT2执行MatthieuM的建议。

您还可以通过接受要插入的类型,带有默认参数,但可能会引发附加副本。

当你有一个指向某个类的对象的指针或引用时,你只知道它引用的实际运行时对象要么是该类,要么是某个派生类auto在编译时无法知道对象的运行时类型,因为包含auto变量的代码段可能在一个运行两次的函数中——一次处理一个运行时类型的对象,另一次处理不同运行时间类型的对象!类型系统不能告诉你在一种具有多态性的语言中,确切的类型是什么——它只能提供一些约束。

如果您知道对象的运行时类型是某个特定的派生类(如您的示例中所示),则可以(也必须)使用强制转换。(人们认为最好使用static_cast<Position*>形式的强制转换,因为强制转换是危险的,这样可以更容易地在代码中搜索强制转换。)

但总的来说,经常这样做是糟糕设计的标志。声明基类并从中派生其他类类型的目的是使的对象能够以相同的方式处理这些类型中的所有,而不必强制转换为特定类型。

  • 如果您希望在编译时始终拥有正确的派生类型,而不使用强制转换,那么您别无选择,只能使用该类型的单独集合。在这种情况下,从Storage导出Position可能没有意义
  • 如果您可以重新排列事物,使StorageManager::Get()的调用者需要用Position做的一切都可以通过调用不指定Position特定信息(如坐标)的函数来完成,那么您可以在Storage中将这些函数变成虚拟函数,并在Position中实现Position特定版本的函数。例如,您可以创建一个函数Storage::Dump(),将其对象写入stdoutPosition::Dump()将输出XY,而用于其他可想到的派生类的Dump()的实现将输出不同的信息
  • 有时,您需要能够处理一个对象,该对象可能是几个本质上不相关的类型之一。我怀疑这里可能就是这样。在这种情况下,boost::variant<>是一个很好的方法。这个库提供了一个强大的机制,称为Visitor模式,它允许您指定应该对variant对象可能是的每种类型采取什么操作

除了这个想法看起来很糟糕之外。。。让我们看看我们能做些什么来改善这种情况。

=>要求默认构造是个坏主意

template <typename T>
T& add(std::string const& name, std::unique_ptr<T> element) {
T& t = *element;
auto result = map.insert(std::make_pair(name, std::move(element)));
if (result.second == false) {
// FIXME: somehow add the name here, for easier diagnosis
throw std::runtime_error("Duplicate element");
}
return t;
}

=>盲目降低是个坏主意

template <typename T>
T* get(std::string const& name) const {
auto it = map.find(name);
return it != map.end() ? dynamic_cast<T*>(it->second.get()) : nullptr;
}

但坦率地说,这个体系漏洞百出。而且可能一开始就没有必要。我鼓励你复习一下一般问题,想出一个更好的设计。