临时数据成员的生命周期延长和 API 设计

Lifetime extension of temporaries' data members and API design

假设我有一个跨平台 Path class 比如:

class Path {
public:
    // ...
    Path parent() const;                // e.g., /foo/bar -> /foo

    std::string const& as_utf8() const {
        return path;
    }
private:
    std::string path;
};

parent() 成员函数 return 是 this 路径的父路径,因此它(正确地)return 是一个新构造的 Path 对象代表它。

对于将 OS 级别的路径表示为 UTF-8 字符串的平台(例如,Unix),as_utf8() 到 return 直接引用似乎是合理的内部表示 path 因为它 已经 UTF-8.

如果我有这样的代码:

std::string const &s = my_path.as_utf8();  // OK (as long as my_path exists)
// ...
Path const &parent = my_path.parent();     // OK (temporary lifetime extended)

这两行都很好,因为:

到目前为止,还不错。但是,如果我有这样的代码:

std::string const &s = my_path.parent().as_utf8(); // WRONG

那么这是 错误的 因为 parent() 编辑的临时对象 return 没有 有它的生命周期扩展是因为 const& 而不是 指的是临时对象,而是它的数据成员。此时,如果您尝试使用 s,您将得到垃圾或核心转储。如果代码是:

    std::string as_utf8() const {                 // Note: object and NOT const&
        return path;
    }

那么代码就是正确的。但是,每次调用此成员函数时都创建一个临时对象是低效的。这意味着 no "getter" 成员函数应该 ever return 引用它们的数据成员。

如果 API 保持原样,那么调用者必须查看 as_utf8() 的 return 类型似乎会给调用者带来不必要的负担return是否是const&:如果是,那么调用者必须使用对象并且不是 const&;如果它 return 是一个对象,那么调用者 可以 使用 const&.

那么有什么方法可以解决这个问题,使 API 在大多数情况下既有效又能防止用户从看似无害的代码中获取悬空引用?


顺便说一下,这是使用 g++ 5.3 编译的。临时 的生命周期可能 应该延长,但编译器有一个错误。

您可以做的是创建 2 个不同版本的 as_utf8(),一个用于左值,一个用于右值。不过你需要 C++11。

这样,您就可以两全其美:const& 当对象不是临时对象时,有效移动:

std::string const& as_utf8() const & {
                               // ^^^ Called from lvalues only
    return path;
}

std::string as_utf8() const && {
                        // ^^^^ Called from rvalues only
    return std::move(path); //We don't need path any more
}

在我看来,关于 return 引用还是对象的指导原则是检查原始 class.

的定义角色

即方法是公开一个简单的 属性 (争论一个参考,特别是如果它是不可变的),还是 生成 东西?

如果它正在生成一个新的对象或表示,我们可以合理地期望它 return 一个不同的对象。

API 的用户通常习惯于理解属性不会超过其宿主对象的寿命。这当然可以在文档中明确说明。

例如

struct path
{
    /// a property
    /// @note lifetime is no longer than the lifetime of this object
    std::string const& native() const;

    /// generate a new string representation in a different format
    std::string to_url() const;

};

在这种情况下,我个人会避免使用 as_ 前缀,因为对我来说,这表明我们正在 return 对同一对象进行新的表示,例如:

struct world 
: std::enable_shared_from_this<world>
{
    struct sky {} my_sky_;

    /// returns a shared_ptr to my sky object, which shares its lifetime
    /// with this world.
    std::shared_ptr<sky> as_sky() 
    { 
      return std::shared_ptr<sky>(shared_from_this(), std::addressof(my_sky_));
    }
};