私有类成员的延迟初始化的最佳实践

时间:2016-02-17 14:11:19

标签: c++ constructor

是否有针对类M的私有类成员C的延迟初始化的最佳做法?例如:

class C {
public:
    C();

    // This works properly without m, and maybe called at any time,
    // even before startWork was called.
    someSimpleStuff();

    // Called single time, once param is known and work can be started.
    startWork(int param); 

    // Uses m. Called multiple times.
    // Guaranteed to only be called after startWork was called 
    doProcessing(); 

private:
    M m;
};

class M {
    M(int param);
};

无法构建类C的对象,因为M没有默认初始值设定项。

如果您可以修改M的实施,则可以向init添加M方法,并使其构造函数不接受任何参数,允许构造类C的对象。

如果没有,您可以将C成员m包裹在std::unique_ptr中,并在可能的情况下构建它。

但是,这两种解决方案都容易出现错误,这些错误会在运行时捕获。是否有一些练习可以确保编译时 m仅在初始化后使用?

限制:C类的一个对象被传递给使用其公共接口的外部代码,因此C的公共方法不能分成多个类。

5 个答案:

答案 0 :(得分:5)

最佳做法是从不使用延迟初始化。

在您的情况下,抛弃C的默认构造函数并将其替换为C(int param) : m(param){}。也就是说,类成员使用基本成员初始化在构造点初始化。

使用延迟初始化意味着您的对象可能处于未定义状态,并且实现并发等事情更难。

答案 1 :(得分:2)

#define ENABLE_THREAD_SAFETY

class C {
public:
    C();

    // This works properly without m, and maybe called at any time,
    // even before startWork was called.
    someSimpleStuff();

    // Called single time, once param is known and work can be started.
    startWork(int param); 

    // Uses m. Called multiple times.
    // Guaranteed to only be called after startWork was called 
    doProcessing(); 

    M* mptr()
    {
#ifdef ENABLE_THREAD_SAFETY
       std::call_once(create_m_once_flag, [&] {
          m = std::make_unique<M>(mparam);
       });
#else
        if (m == nullptr)
          m = std::make_unique<M>(mparam);
#endif
       return m.get();
    }
private:
    int mparam;
    std::unique_ptr<M> m;
#ifdef ENABLE_THREAD_SAFETY
    std::once_flag create_m_once_flag;
#endif
};

class M {
    M(int param);
};

现在你所要做的就是直接停止使用m,然后通过mptr()来访问它。它只会在第一次使用时创建一次。

答案 2 :(得分:1)

我会选择unique_ptr ...你在哪里看到问题?使用M时,您可以轻松查看:

if(m)
    m->foo();

我知道这不是编译时检查,但据我所知,目前的编译器无法检查。代码分析必须非常复杂,因为你可以在任何方法中初始化m,或者 - 如果是公共/受保护的 - 甚至在另一个文件中。编译时检查意味着延迟初始化是在编译时完成的,但延迟初始化的概念是基于运行时的。

答案 3 :(得分:1)

根据我对您的问题的理解,这是一个解决方案吗?

您将不需要M的功能放入class D。您创建D对象并使用它。在您需要M并且想要执行doProcessing()代码后,您需要创建C的对象,将D传递给它并使用param初始化它现在有了。

以下代码仅用于说明这个想法。在这种情况下,您可能不需要startWork()成为单独的函数,其代码可以在C的构造函数中编写

注意:我已将所有函数都清空,因此我可以编译代码以检查语法错误:)

class M
{
public:
    M(int param) {}
};

class D
{
public:
    D() {}

    // This works properly without m, and maybe called at any time,
    // even before startWork was called.
    void someSimpleStuff() {}
};


class C
{
public:
    C(D& d, int param) : d(d), m(param) { startWork(param); }

    // Uses m. Called multiple times.
    // Guaranteed to only be called after startWork was called
    void doProcessing() {}

private:
    // Called single time, once param is known and work can be started.
    void startWork(int param) {}

    D& d;
    M m;
};

int main()
{
    D d;
    d.someSimpleStuff();

    C c(d, 1337);
    c.doProcessing();
    c.doProcessing();
}

答案 4 :(得分:0)

问题是&#34;是否有可能在编译时检查m仅在初始化之后使用而不拆分C接口?&#34;

答案是,您必须使用类型系统来确保在初始化之前不使用对象M,这意味着要拆分C接口。在编译时,编译器只知道对象的类型和常量表达式的值。 C不能是文字类型。所以你必须使用类型系统:你必须拆分C接口,以确保在编译时M只在初始化后使用。