C ++-使用模板通过引用/值传递

时间:2020-03-12 21:43:52

标签: c++ templates

上下文

将参数传递给函数时,我们可以传递引用传递值

通过引用传递的一个优点是,该参数不需要通过自身的副本来实现,这对于包含大量数据的自定义对象似乎是有利的。

但是对于多个类接口,为每个接口创建一个按引用传递函数可能会很……不整洁,例如:

void parse(Console&);
void parse(Dialog&);
void parse(Image&);
void parse(Window&);
void parse(...); // how many more overloads?!

那么,使用模板来解决这个不整洁的问题,对吧?

template <typename type> void parse(type&);

但是我碰到了路标……


尝试的解决方案(代码)

#include <iostream>

class Object { public:
    // Copy/ Move constructors implicitly defined;
    //   Same goes for the assignment operator.
    constexpr inline Object(void) {}

    // Test if the object can be a reference or is an expression value.
    template <typename type>
    inline static void parse(type) noexcept { std::cout << "[pass-by-value]" << std::endl; }

    template <typename type>
    inline static void parse(type&) noexcept { std::cout << "[pass-by-reference]" << std::endl; }
};

按值传递对象有效

Object::parse(Object()); // SUCCESS: "pass-by-value"

但是使用引用会导致测试函数与其重载冲突。

Object object {};
Object::parse(object); // ERROR: Ambiguous call of `Object::parse` function.
                       // EXPECTED: "pass-by-reference"

我认为通话不明确,因为
parse(type)重载由{type=Object&}
初始化 parse(type&)重载用{type=Object}初始化。

尽管如此,我认为应该优先使用parse(type&)重载,但是显然情况并非如此。

那么,我在这里想念什么?


问题

使用模板函数如何区分可以作为引用的参数还是作为常量表达式的参数?

注意:我正在尝试防止使用用户定义的对象进行不必要的复制,即:

如果我通过 Image() [type=Image] 作为参数,它将按值传递。
否则它将通过引用传递,例如:

Image image; // -> image [type=Image&]
Image images[1]; // -> images[0] [type=Image&]
Image *image_p; // -> *(image_p + 0) [type=Image&]

2 个答案:

答案 0 :(得分:2)

基于您的评论,似乎您想要的是r值引用的重载(以便您可以区分何时传递了临时对象或何时传递了现有对象)

void foo(int& x){
    std::cout << x << " was an l-value\n";
}

void foo(int&& x){
    std::cout << x << " was an r-value (possibly a temporary or std::move result)\n";
}

int main(){
    int x = 5;
    foo(x);
    foo(10);
}

请注意,第二个版本也不会执行复制。构造一个临时对象,然后使用移动语义。


如果您不想修改原始对象并且不关心区分,只需将参数设为const引用

void foo(const int& x){
    std::cout << x << " may or may not have been a temporary "
                      "(it's lifetime is prolonged until end of this function)\n";
}


int main(){
    int x = 5;
    foo(x);
    foo(10);
}

这里也不需要副本。

答案 1 :(得分:1)

这是一个普遍的问题。

您可以根据对象的大小以及它是否是可复制的来废止这些方法。如果它不容易复制或太大,请使用const引用-否则请使用copy。

这样,您就不会有歧义要解决-只需利用SFINEA启用/禁用某些功能即可。