当一个对象保证比其包含的对象寿命更长时,该如何存储它?

时间:2015-11-06 20:55:28

标签: c++ c++11 memory-management smart-pointers

在处理项目时,当传入的对象通过其构造函数传递给另一个对象时,我遇到了一个有趣的问题,即传入的对象(在内存生存期方面)保证比对象对象更长。请记住,我仍在学习C ++ 11 / C ++ 14的细节,所以我正在寻找有助于理解内存管理和C ++ 11生命周期的建设性讨论C ++ 14风格的语义。

此问题的设置如下:

class TopLevelClass {
public:
    void someMethod (int someValue) {
        // Do some work
    }

    std::unique_ptr<Context> getContext () {
        return std::make_unique<Context>(this);
    }
};

class Context {
public:
    Context (TopLevelClass* tlc) : _tlc(tlc) {}

    void call (int value) {
        // Perform some work and then call the top level class...
        _tlc->someMethod(value);
    }

protected:
    TopLevelClass* _tlc;
};

虽然此设置的有效替代方法是将TopLevelClass作为参数传递到call类的Context方法,但在我说明的方案中这是不可能的:可以访问Context对象的客户端代码可能无法访问TopLevelClass对象。

虽然上面说明的代码功能正确满足我的需求,但我觉得好像存在代码味道。也就是说,将句柄存储为TopLevelClass对象作为原始指针并不表示Context类不负责管理此指针的生命周期(因为在这种情况下,TopLevelClass 1}}保证比任何Context对象寿命更长。另外,使用C ++ 11时,我对使用原始指针而不是智能指针犹豫不决(根据Scott Meyer在 Effective Modern C ++ 中的建议)。

我探索的一个替代方法是使用共享指针将句柄传递给TopLevelClass,并将此句柄作为共享指针存储在Context类中。这要求TopLevelClass以下列方式从std::enabled_shared_from_this继承:

class TopLevelClass : public std::enable_shared_from_this<TopLevelClass> {
public:
    // Same "someMethod(int)" as before...

    std::unique_ptr<Context> getContext () {
        return std::make_unique<Context>(shared_from_this());
    }
};

class Context {
public:
    Context (std::shared_ptr<TopLevelClass> tlc) : _tlc(tlc) {}

    // Same "call(int)" as before...

protected:
    std::shared_ptr<TopLevelClass> _tlc;
};

此方法的缺点是除非std::shared_ptr 先验存在TopLevelClass,否则将抛出std::bad_weak_ptr异常(有关详细信息,见this post)。因为在我的情况下,代码中没有创建std::shared_ptr<TopLevelClass>,所以我无法使用std::enable_shared_from_this<T>方法:我只能使用{{1}返回TopLevelClass的单个实例根据我的项目要求,原始指针如下

static

是否存在一种方法,表明static TopLevelClass* getTopLevelClass () { return new TopLevelClass(); } 不负责管理Context实例的句柄,因为TopLevelClass将保证比更长久任何 TopLevelClass对象?我也乐于接受改变设计的建议,只要设计变更不会使上述设计的简单性过于复杂化(即创建许多不同的类以便简单地通过单指针到Context)的构造函数。

感谢您的帮助。

2 个答案:

答案 0 :(得分:3)

传入原始指针的方式绝对应该意味着没有所有权转移。

如果你听过somone说“不要使用原始指针”你可能错过了部分句子 - 它应该是“不要使用拥有原始指针”,即不应该在某个地方,你有一个原始指针,你需要调用删除。除了可能在某些低级代码中。如果您知道被指向的对象比获取指针的对象更长,那么传入指针绝对没有错。

您正在说“即,将TopLevelClass对象的句柄存储为原始指针并不表示Context类不负责管理此指针的生命周期”。 相反,存储原始指针意味着这一点 - “此对象不管理此指针所指向的对象的生命周期”。在C ++ 98样式代码中,它并不一定意味着。

使用指针的另一种方法是使用引用。但是有一些警告,因为你必须在构造函数中初始化它,例如它不能像指针一样设置为nullptr(这也是一件好事)。即:

class TopLevelClass {
public:
    void someMethod (int someValue) {
        // Do some work
    }

    std::unique_ptr<Context> getContext () {
        return std::make_unique<Context>(*this);
    }
};

class Context {
public:
  Context(TopLevelClass &tlc) : _tlc(tlc) {}

  void call (int value) {
    // Perform some work and then call the top level class...
    _tlc.someMethod(value);
  }

private:
  TopLevelClass &_tlc;
};

以下是有关该主题的一些文章:

C ++核心指南:

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rr-ptr

Herb Sutter的一些早期文章:

http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/

http://herbsutter.com/2013/05/30/gotw-90-solution-factories/

http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/

http://herbsutter.com/elements-of-modern-c-style/

可能还有很多来自CppCon以及Cpp和Beyond的视频,但我有点懒得去谷歌适当的。

答案 1 :(得分:1)

一种选择是使用类型定义来传达非所有权:

#include <memory>

template<typename T>
using borrowed_ptr = T *;

class TopLevelClass;

class Context {
public:
  Context(borrowed_ptr<TopLevelClass> tlc)
    : _tlc(std::move(tlc))
  { }

private:
  borrowed_ptr<TopLevelClass> _tlc;
};

class TopLevelClass {
public:
  std::unique_ptr<Context> getContext() {
    return std::make_unique<Context>(this);
  }
};

尽管_tlc仍然可以直接转换为原始指针,但它干净地表达了意图。我们可以创建一个名为borrowed_ptr的实际类(类似于shared_ptr),它可以更好地隐藏原始指针,但在这种情况下似乎有些过分。