非指针类成员:它有多好?

时间:2018-05-31 09:19:41

标签: c++ c++14

我有一个非指针类成员,我需要在构造函数中初始化:

class Alerter {

protected:
  Timer timer;

public:
  Alerter(int interval);
};

然后

Alerter::Alerter(int interval) {
    timer = createTimer(interval);
}

(简化代码只是为了演示问题)。

我有一些疑问和顾虑timer可能是首先使用其无参数构造函数创建的,稍后该实例将被createTimer函数返回的内容覆盖。

这种做法有多好?可能的答案可能是:

  • 由无参数构造函数创建的“空计时器”实际上并未创建,因为编译器足够聪明,可以找到我们从未 在覆盖值之前引用它。
  • 创建了空计时器,但这没关系,因为编写良好的代码应支持一次性实例的廉价无参数构造函数。
  • 最好使用指针。

哪些假设(或其他可能的东西)最正确?

2 个答案:

答案 0 :(得分:11)

timer首先是默认构造的,然后分配。当然,您可以假设Timer对于默认构造有多便宜或者编译器优化,但是在这里您不需要这个,因为可以通过使用初始化列表来阻止默认构造:

Alerter::Alerter(int interval) : timer(createTimer(interval)) { }

除非你的Timer类是可复制的,但不是可复制构造的,否则这将是有效的。这将是奇怪的。

答案 1 :(得分:3)

将调用两个构造函数(然后也分配)。

我扩展了你的例子:

class Timer
{
  public:
    Timer() : m_interval(0)
    {
        std::cout << "Constructor withOUT parameter has been called." << std::endl;
    }

    Timer(int interval) : m_interval(interval)
    {
        std::cout << "Constructor with parameter has been called." << std::endl;
    }

    Timer(const Timer& timer)
    {
        std::cout << "Copy constructor has been called." << std::endl;
        m_interval = timer.m_interval;
    }

    Timer(Timer&& timer)
    {
        std::cout << "Move constructor has been called." << std::endl;
        m_interval = timer.m_interval;
    }

    void operator=(Timer&& timer)
    {
        std::cout << "Move assignment has been called." << std::endl;
        m_interval = timer.m_interval;
    }

  private:
    int m_interval;
};

class Alerter
{
  protected:
    Timer timer;

  public:
    Alerter(int interval)
    {
        timer = Timer(interval);
    }
};

... 
Alerter alerter(12);
...

输出:

Constructor withOUT parameter has been called.
Constructor with parameter has been called.
Move assignment has been called.

回答你的问题。如果成员变量只初始化一次会更好,但这不是一个硬性规则。例如,如果Timer是一个非常复杂的对象,但默认构造函数只创建一个虚拟对象,那么它并不太昂贵且可以接受。

所以,如果你能避免,最好避免。使用指针是一种可能的解决方案,但请注意以下几点:

  • 最好使用std::unique_ptr来避免内存泄漏。
  • 这肯定需要堆内存分配操作。如果对象无论如何都在堆上(例如使用复杂的内部结构,如STL容器),那么这不是问题。但是如果对象非常简单并且主要用例是将它们创建为本地对象,则会导致相当大的性能损失。这应该是一个有意识的设计决定。