任何人都可以看到从工厂按值返回对象而不是返回unique_ptr有什么问题吗?
以下编译并正确地为我运行,但我不确定我是否错过了关于l值和r值参考交互和生命周期的事情。
我有以下类层次结构,
#include <iostream>
struct Reader {
virtual ~Reader() {}
virtual void get(int& val) = 0;
};
struct CinReader : public Reader {
~CinReader() { std::cout << "~CinReader()\n"; }
void get(int& val) override
{
std::cout << "CinReader::get\n";
std::cin >> val;
}
};
struct ConstantReader : public Reader {
void get(int& val) override
{
std::cout << "ConstantReader::get\n";
val = 120;
}
};
我使用以下工厂返回实例
enum class ReaderType { cin = 0, constant = 1 };
Reader&& readerFactoryObj(ReaderType type)
{
switch (type) {
case ReaderType::cin:
return std::move(CinReader());
break;
case ReaderType::constant:
return std::move(ConstantReader());
break;
default:
throw "Unknown Reader type";
}
}
并按如下方式使用,
int main(void)
{
auto&& reader = readerFactoryObj(ReaderType::cin);
int val;
reader.get(val);
}
传递给存储对Reader
接口
class Doubler
{
public:
Doubler(Reader& reader) : mReader(reader) { }
void doubleNum() const;
private:
Reader& mReader;
};
void Doubler::doubleNum() const
{
int val;
mReader.get(val);
std::cout << val * 2 << '\n';
}
int main(void)
{
auto&& reader = readerFactoryObj(ReaderType::cin);
Doubler d(reader);
d.doubleNum();
}
我意识到reader
现在不属于Reader
类型,但它将是其具体的子类之一。
将reader
作为Reader&
传递并存储是否存在任何问题?
更新: 我在CinReader中添加了一个析构函数,它清楚地告诉我它的生命周期在它被使用之前结束。
答案 0 :(得分:3)
您正在返回对临时对象的引用。未定义的行为。如果您没有通过完全冗余的move
调用来欺骗它,编译器可能会警告您。
答案 1 :(得分:3)
您可以从工厂职能部门返回两件事:
std::unique_ptr
或std::shared_ptr
,不是原始指针,因此所有权语义是明确的,RAII可以工作。std::make_shared
,std::allocate_shared
,依此类推。你必须做的就是返回一个存储的引用或指针,它将在返回时被清理,就像堆栈变量一样。
编译器可能无法捕获您,它似乎可以工作(一段时间),but it's undefined behavior。
在你的情况下,通过指针返回将是正确的,因此多态性可以工作。
顺便说一句,不要抛出字符串文字,抛出std::exception
或派生。
答案 2 :(得分:2)
任何人都可以看到从工厂按值返回对象而不是返回unique_ptr有什么问题吗?
一个有效的问题,但您的工厂没有按价值返回。它的返回类型是引用类型,因此您可以通过引用返回。
传递给存储对Reader接口的引用的东西
时有效
不,它似乎只能起作用,因为你的Reader
类型是无状态的,所以你碰巧在它们的生命周期结束后使用它们。
Reader&& readerFactoryObj(ReaderType type)
{
...
return std::move(CinReader());
您正在创建一个临时(这是一个右值),然后使用std::move
将其强制转换为右值(这完全是多余的)然后返回对该临时值的悬空引用。这真的很糟糕。不要这样做。如初。
如果你想要移动语义的效率和简单性,那么只需让你的函数按值返回并说return x;
,它就更容易做正确的事情,不像做不必要的强制转换和返回悬空引用,这是更复杂和错误。
在您的具体情况下,由于您的工厂想要返回两种不同的类型,您无法按值返回,或者您slice该对象并且只返回基本部分。所以你需要通过指针返回,你最好通过智能指针返回,否则孩子们会指着你在街上嘲笑你。
答案 3 :(得分:1)
设计问题是工厂通常返回一个未指定类型的对象,至少实现所需的接口(基类)。但是,它可能(并且经常会)返回更多派生类的对象。
按值返回此类对象会导致切片。返回引用会导致生命周期问题(如代码所示)。返回智能指针可以修复这些生命周期问题。