【问题标题】:Reference lost on forwarding (and no auto-conversion to save me) - why?转发时参考丢失(并且没有自动转换来拯救我) - 为什么?
【发布时间】:2016-04-29 01:46:23
【问题描述】:

我正在编写一个工厂,用于使用基类的名称生成子类的实例,并将这个(模板化)工厂与我的类 Foo 一起使用。没关系整个代码,但本质上,工厂有一个从字符串到创建实例的函数的映射;模板参数控制这些函数采用哪些参数。在我的例子中,Foo 的 ctor 和 foo 的任何子类都采用 const Bar&,这就是 ctor 参数的可变参数模板所包含的内容。

我有:

template<typename SubclassKey, typename T, typename... ConstructionArgs>
class Factory {
public:
    using Instantiator = T* (*)(ConstructionArgs&&...);
private:
    template<typename U>
    static T* createInstance(ConstructionArgs&&... args)
    {
        return new U(std::forward<ConstructionArgs>(args)...);
    }
    using Instantiators = std::unordered_map<SubclassKey,Instantiator>;
    Instantiators subclassInstantiators;

public:
    template<typename U>
    void registerSubclass(const SubclassKey&  subclass_id)
    {
        static_assert(std::is_base_of<T, U>::value,
            "This factory cannot register a class which is is not actually "
            "derived from the factory's associated class");
        auto it = subclassInstantiators.find(subclass_id);

       if (it != subclassInstantiators.end()) {
            throw std::logic_error("Repeat registration of the same subclass in this factory.");
        }
        subclassInstantiators.emplace(subclass_id, &createInstance<U>);
    }
};

根据大众的需求,这里还有……

class Foo {
    using FooFactory = Factory<std::string, Foo, const Bar&>;
private:
    static FooFactory& getTestFactory() {
        static FooFactory kernel_test_factory;
        return kernel_test_factory;
    }
    //...
public:
    //...
    template <typename U>
    static void registerInFactory(const std::string& name_of_u) {
        Foo::getTestFactory().registerSubclass<U>(name_of_u);
    }
    Bar bar;
    Foo(const Bar& bar_) : bar(bar_) { };
    virtual ~Foo() {}
    // ...
};

class NiceFoo : public Foo {
    // no ctors and dtors
};

不幸的是,由于某种原因,当我打电话时

我收到关于 ctor 期望 const Bar&amp; 的投诉,而我应该在 createInstance 中提供的参数列表实际上是 const Bar

问题:

  • 为什么引用“消失”了?
  • 我做错了吗?我应该以不同的方式处理这个问题吗?

GCC 错误输出:

/home/joeuser/myproj/../util/Factory.h(36): error: no instance of constructor "NiceFoo::NiceFoo" matches the argument list
            argument types are: (const Bar)
          detected during:
            instantiation of "T *util::Factory<SubclassKey, T, ConstructionArgs...>::createInstance<U>(ConstructionArgs &&...) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]" 
(59): here
            instantiation of "void util::Factory<SubclassKey, T, ConstructionArgs...>::registerSubclass<U>(const SubclassKey &) [with SubclassKey=std::string, T=Foo, ConstructionArgs=<const Bar &>, U=NiceFoo]" 
/home/joeuser/myproj/../Foo.h(79): here
            instantiation of "void Foo::registerInFactory<U>(const std::string &) [with U=NiceFoo]" 
/home/joeuser/myproj/NiceFoo.cpp(122): here

【问题讨论】:

  • 请发帖minimal reproducible examplecreateInstance 是班级成员吗?类模板成员? ConstructionArgs是类模板模板参数吗?函数模板模板参数? NiceFoo 期待什么?等等。
  • @Angew:问题是,一个完整的例子不会很少。但我会提出整个工厂课程。为了回答你的问题,ConstructionArgs 是一个可变参数模板参数,NiceFoo ctor 需要一个 const Bar&amp; 就像所有 Foos
  • 顺便说一句,MCVE 现在缺少的只是FooNiceFoo 和呼叫站点的(简单)定义。没有那么多代码。 (我假设您在一些地方也遗漏了template &lt;class U&gt;)。
  • 您的NiceFoo 中需要一个using Foo::Foo;
  • @einpoklum 这说明了为什么发布 MCVE 很重要:有时,错误出乎意料。

标签: templates c++11 factory variadic-templates perfect-forwarding


【解决方案1】:

您的代码中有两个问题:直接问题和基本问题。

直接的一个是NiceFoo 没有声明任何构造函数。您声称“它继承了Foo 的构造函数”,但在您显示的代码中并非如此。继承构造函数将通过以下方式实现:

class NiceFoo : public Foo {
public:
  using Foo::Foo;
}

如果没有 using 声明,您只会有一个没有声明构造函数的类(它将默认生成复制、移动和无参数构造函数,但不会生成新构造函数)。


您的设计中还有一个基本问题:您试图在完美转发中使用类模板的模板参数。那是行不通的;完美转发依赖于模板参数推导,这仅发生在函数模板中。

换句话说,ConstructionArgs&amp;&amp;... 不会创建转发引用,而是创建普通的旧右值引用。在您提出的具体情况下,这并不重要,因为 ConstructionArgsconst Bar &amp; 并且引用折叠可以解决问题。但是,如果您在构造函数参数中包含值类型(而不是引用类型),createInstance 将一直使用纯右值引用,并且从左值初始化将是不可能的。

正确的解决方案是让ConstructionArgs 镜像完全 T 的构造函数所期望的 - 换句话说,从使用 ConstructionArgs 的任何地方删除 &amp;&amp;,并在createInstance 中使用std::move 而不是std::forward。一念之间,std::forward 应该能够留下来做正确的事;基本上相当于左值引用的无操作,以及值和右值引用的std::move

【讨论】:

  • 但是std::move() 不会尝试从我通过的东西中移出东西吗?为什么std::move() 会处理任何 const 的东西?
  • @einpoklum 我刚刚谈到了这一点。但唯一重要的是非const 左值引用。对于值,std::forwardstd::move 是相同的。对于const Xyz&amp;std::move 会导致const Xyz&amp;&amp;,它无法绑定到正常的移动操作,但确实绑定到const Xyz&amp;,从而再次产生正确的结果。
  • 另外...我应该把 && 放在任何地方,还是只放在某些地方?
  • @einpoklum 它在您显示的代码中的两个地方使用,并且在它们中都应该被删除。
猜你喜欢
  • 2011-03-01
  • 2016-09-05
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多