函数参数的C ++所有权

时间:2019-06-18 14:33:56

标签: c++ ownership

我写了一个具有这种形式的函数:

Result f( const IParameter& p);

我的意图是,此签名可以清楚地表明该函数未获得参数p的所有权。

问题在于Result将保留对IParameter的引用:

class Result
{  
    const IParameter& m_p;

public: 
    Result( const IParameter& p ) 
        : m_p( p ){ } 
};

但是随后发生的是有人调用了这样的函数:

const auto r = f(ConcreteParameter{});

不幸的是,临时文件可以绑定到const引用,这导致崩溃。

问题是:如何弄清楚该函数不应与临时函数一起调用,并且在发生这种情况时可能会出现很好的编译错误?在这种情况下,声明它没有所有权,因为传递给它的结果会传播到函数调用范围之外,这实际上是错误的吗?

5 个答案:

答案 0 :(得分:8)

最简单的方法是使用右值引用参数重载该函数。那些优先于临时引用的const引用,因此将选择它们。如果然后删除所说的重载,则会得到一个不错的编译器错误。对于您的代码,如下所示:

Result f( const IParameter&& ) = delete;

您也可以使用Result做同样的事情来修饰它,看起来像这样:

class Result
{  
    const IParameter& m_p;

public: 
    Result( const IParameter& p ) 
        : m_p( p ){ } 
    Result( const IParameter&& ) = delete;
};

答案 1 :(得分:3)

您可以从重载集中手动删除接受IParameter&&右值的构造函数:

class Result
{
    // ...

    public:
    Result( IParameter&& ) = delete; // No temporaries!
    Result( const IParameter& p );
};

当客户端代码尝试通过实例化对象时

Result f(ConcreteParameter{}); // Error

采用const限定引用的构造函数由于缺少const-ness而没有匹配,但是右值构造函数完全匹配。由于这个= delete d,编译器拒绝接受这样的对象创建。

请注意,如评论中所指出的那样,可以使用const限定的临时人员来规避,有关如何确保不会发生这种情况的信息,请参见@NathanOliver's answer

还请注意,并非所有人都同意这是一种好习惯,例如,看看here(在15:20)。

答案 2 :(得分:3)

很难说。最好的方法是证明Result的生存期不能超过构造它的IParameter的生存期。

在某些情况下,临时有效的临时构造函数是有效的。考虑一下:

doSomethingWithResult(Result{SomeParameterType{}});

删除具有临时性的构造函数会阻止这样的有效代码。

此外,删除右值构造函数不会阻止所有情况。考虑一下:

auto make_result() -> Result {
    SomeParameterType param;
    return Result{param};
}

即使删除了带有临时函数的构造函数,无效代码仍然非常容易制作。无论如何,您 都必须记录参数的寿命要求。

因此,如果您仍然要记录此类行为,我将选择标准库对string views的作用:

int main() {
    auto sv = std::string_view{std::string{"ub"}};
    std::cout << "This is " << sv;
}

它不会阻止从临时字符串构造字符串视图,因为它很有用,就像我的第一个示例一样。

答案 3 :(得分:3)

通常,如果函数通过const&接收值,则可以预期该函数将使用该值,但不会保留该值。您确实持有对值的引用,因此您可能应该更改参数类型以使用shared_ptr(如果资源是必需的)或weak_ptr(如果资源是可选的)。否则,您会不时遇到此类问题,因为没人会阅读文档。

答案 4 :(得分:0)

我已经将@NathanOliver答案投票为最佳答案,因为我真的认为它已提供了我提供的信息。另一方面,当功能比我最初的示例中的功能更复杂时,我想分享我认为是解决此特定情况的更好解决方案。

$Subnet1解决方案的问题在于,它随着参数的数量呈指数增长,假设在函数调用结束后所有参数都需要保持活动状态,并且您需要进行编译时检查以确保您的用户API并未尝试将这些参数的所有权提供给函数:

delete

我们需要void f(const A& a, const B& b) { // function body here } void f(const A& a, B&& b) = delete; void f(A&& a, const B& b) = delete; void f(A&& a, B&& b) = delete; 所有可能的组合,从长远来看,这将很难维持。因此,我提出的解决方案是利用以下事实:通过移动包装delete的{​​{1}}构造函数已在STD中删除,然后编写:

reference_wrapper

这样,所有无效的过载将被自动删除。到目前为止,我认为这种方法没有任何缺点。