我是否必须从工厂返回指针?

时间:2014-12-19 09:09:45

标签: c++11 factory rvalue-reference

任何人都可以看到从工厂按值返回对象而不是返回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中添加了一个析构函数,它清楚地告诉我它的生命周期在它被使用之前结束。

4 个答案:

答案 0 :(得分:3)

您正在返回对临时对象的引用。未定义的行为。如果您没有通过完全冗余的move调用来欺骗它,编译器可能会警告您。

答案 1 :(得分:3)

您可以从工厂职能部门返回两件事:

  1. 指针。 std::unique_ptrstd::shared_ptr,不是原始指针,因此所有权语义是明确的,RAII可以工作。
  2. 对象本身的值。例如,请查看std::make_sharedstd::allocate_shared,依此类推。
  3. 你必须做的就是返回一个存储的引用或指针,它将在返回时被清理,就像堆栈变量一样。
    编译器可能无法捕获您,它似乎可以工作(一段时间),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)

设计问题是工厂通常返回一个未指定类型的对象,至少实现所需的接口(基类)。但是,它可能(并且经常会)返回更多派生类的对象。

按值返回此类对象会导致切片。返回引用会导致生命周期问题(如代码所示)。返回智能指针可以修复这些生命周期问题。