临时数据成员的寿命延长和 API 设计

Lifetime extension of temporaries' data members and API design

本文关键字:API 设计 数据成员      更新时间:2023-10-16

假设我有一个跨平台的Path类,如:

class Path {
public:
    // ...
    Path parent() const;                // e.g., /foo/bar -> /foo
    std::string const& as_utf8() const {
        return path;
    }
private:
    std::string path;
};

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

对于将操作系统级别的路径表示为UTF-8字符串的平台(例如Unix), as_utf8()直接返回对内部表示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)

这两行都可以,因为:

  • 假设my_path存在,则s仍然有效。
  • parent()返回的临时对象的生存期由const&延长。

到目前为止,一切顺利。但是,如果我有如下代码:

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

是错误的,因为parent()返回的临时对象没有延长其生存期,因为const&没有指向临时对象,而是指向它的数据成员。此时,如果您尝试使用s,您将获得垃圾或核心转储。如果代码改为:

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

则代码是正确的。但是,每次调用这个成员函数时都创建一个临时对象是低效的。这也意味着没有"getter"成员函数应该永远返回对其数据成员的引用。

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

那么有没有办法解决这个问题,使API在大多数情况下都是高效的,但防止用户从看似无害的代码中获得悬空引用?


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

您可以做的是创建两个不同版本的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
}

在我看来,关于是返回引用还是返回对象的指导原则是检查原始类的定义角色。

。该方法是否暴露了一个简单的属性(用于引用,特别是如果它是不可变的),或者它是否生成什么?

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

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_的前缀,因为对我来说,它暗示我们返回同一对象的新表示,例如:

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_));
    }
};