8

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

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

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

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

as_utf8()对于在操作系统级别将路径表示为 UTF-8 字符串的平台(例如,Unix),直接返回对内部表示的引用似乎是合理的,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()来查看它是否返回 a似乎会给调用者带来过度的负担const&:如果是,那么调用者必须使用一个对象而不是const&; _ 如果它返回一个对象,那么调用者可以使用const&.

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


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

4

2 回答 2

7

您可以做的是创建 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
}
于 2016-08-24T16:14:47.780 回答
1

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

即该方法是否公开了一个简单的属性(争论一个引用,特别是如果它是不可变的),还是它产生了什么?

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

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;

};

在这种情况下,我个人会避免使用前缀 of 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_));
    }
};
于 2016-08-24T16:36:48.487 回答