【问题标题】:Can you perfectly forward without templates没有模板你能完美转发吗
【发布时间】:2021-01-25 03:13:11
【问题描述】:

我有一个有多个构造函数的类:

class Obj
{
    public:
        Obj(int x, int y)           {}
        Obj(std::string const& str) {}
        Obj(char v, int s)          {}
};

现在我想向存储在对象中的构造函数添加一个 Options 对象。但为了使这项工作顺利进行,我想尽可能移动选项,但在必须时复制。看来我必须将构造函数的数量增加一倍才能支持选项的移动和复制。

class Options {};

class Obj
{
    Options options;

    public:
        Obj(Options const& o, int x, int y):          options(o) {}
        Obj(Options const& o, std::string const& str):options(o) {}
        Obj(Options const& o, char v, int s)         :options(o) {}

        Obj(Options&& o, int x, int y):          options(std::move(o)) {}
        Obj(Options&& o, std::string const& str):options(std::move(o)) {}
        Obj(Options&& o, char v, int s)         :options(std::move(o)) {}
};

如果我使用了模板,我可以使用完美转发并获得正确的效果。

template<typename Options>
class Obj
{
    Options options;

    public:
        Obj(Options&& o, int x, int y):          options(std::forward<Options>(o)) {}
        Obj(Options&& o, std::string const& str):options(std::forward<Options>(o)) {}
        Obj(Options&& o, char v, int s)         :options(std::forward<Options>(o)) {}
};

问题在于我知道Options的类型。

【问题讨论】:

  • 您真的需要完美转发吗?仅按值复制它应该足够快(假设移动的成本可以忽略不计)。

标签: c++ constructor forwarding


【解决方案1】:

转发引用仅适用于模板,您可以模板化构造函数并对模板参数施加限制。

class Obj
{
    Options options;
    template<typename Opt>
    using ValidOption = std::enable_if_t<std::is_same_v<Options, std::decay_t<Opt>>, bool>;


    public:
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, int x, int y):          options(std::forward<Options>(o)) {}
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, std::string const& str):options(std::forward<Options>(o)) {}
        template <typename X = Options, ValidOption<X> = true>
        Obj(X&& o, char v, int s)         :options(std::forward<Options>(o)) {}
};

【讨论】:

  • 这有一个缺点:Obj a({}, 1, 2); 不起作用,因为编译器无法推断出{} 的类型,如果我直接使用Options 会起作用。这意味着我将负担从自己转移到班级的用户身上(我不想这样做)。
  • @MartinYork template &lt;typename X = Options, std::enable_if_t&lt;std::is_same_v&lt;Options, X&gt;&gt;* = nullptr&gt; 工作吗?
  • @songyuanyao 如果您添加 NathanOliver 的技巧(并删除类周围的模板),它可以像我想要的那样工作。仍然丑陋但有效。
  • 顺便说一句,约束std::is_same 非常严格,std::is_constructible 似乎更适应。
【解决方案2】:

你的最后一个sn-p:

template <typename Options>
class Obj
{
    Options options;

public:
    Obj(Options&& o, int, int):            options(std::forward<Options>(o)) {}
    Obj(Options&& o, std::string const&) : options(std::forward<Options>(o)) {}
    Obj(Options&& o, char, int)          : options(std::forward<Options>(o)) {}
};

不是完美的转发,因为模板是你的类,而不是你的函数。

完美的转发应该是:

class Obj
{
    Options options;

public:

    template <typename T> Obj(T&& o, int, int):            options(std::forward<T>(o)) {}
    template <typename T> Obj(T&& o, std::string const&) : options(std::forward<T>(o)) {}
    template <typename T> Obj(T&& o, char, int)          : options(std::forward<T>(o)) {}
};

我在Taking sink parameters by rvalue reference instead of by value to enforce performant usage of interfaces中总结了(性能)选项

所以除非性能真的很重要,甚至额外的动作也很重要,否则我会选择

Obj(Options o, int x, int y) : options(std::move(o)) {}

或

Obj(Options&& o, int x, int y) : options(std::move(o)) {}

(取决于您希望避免隐式复制的重要性)。

如果您以性能为目标,则确实必须使用转发参考。

【讨论】:

  • 我并不担心效率。我正在尝试删除代码重复(或者我正在尝试删除代码重复)。我有一个包含很多类的库,Options 通过多层传递。然后必须在每一层有多个对象的构造函数。代码重复的数量(唯一的区别是移动或通过 ref)呈指数增长。代码通常是微不足道的,但如果我稍后再进行一些小的更改,那么推动该低谷的级联更改可能会很大(并且容易出错)。
  • 所以取值或右值参考是一个不错的选择。
  • 如果对象是 const 会发生什么?是否会调用值版本并强制复制。
  • 如果您的初始对象是 const,则在某处需要一个副本。使用 const lvalue 引用构造函数,它发生在构造函数内部:使用值,它发生在调用站点。使用右值引用,它发生在调用站点并且应该是明确的。
猜你喜欢
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 2014-01-04
  • 2013-01-06
  • 1970-01-01
  • 1970-01-01
  • 2016-03-22
  • 2022-01-22
相关资源
最近更新 更多