为什么类中的静态数据成员在发送到函数时更新不正确

Why Static data member in class updates not correctly when sending it to a function?

本文关键字:函数 不正确 更新 静态 数据成员 为什么      更新时间:2023-10-16

执行后,Goomba::liveGoombas等于某个负值。我对它进行了调试,但不明白为什么它会比构造函数多次启动析构函数。为什么它在这里工作不正确?

// Here is a simple Goomba class. It just keeps track of how many Goombas are alive.
class Goomba
{
public:
  static int liveGoombas;
  Goomba() { liveGoombas++; }
  ~Goomba() { liveGoombas--; }
};
int Goomba::liveGoombas = 0;
// And a Goomba legion class. Please don't change this class.
class GoombaLegion
{
public:
  void add(Goomba goomba)
  {
    goombas.push_back(goomba); //it seems that something wrong in this function
  }
private:
  std::vector<Goomba> goombas;
};
void goombas()
{
  {
    GoombaLegion legion;
  }
  // The legion went out of scope and was destroyed. But how many Goombas are alive?
  std::cout << "There are " << Goomba::liveGoombas << " live goombas" << std::endl;
}

int main()
{
  goombas();
}

如果不指定自己的实现,编译器将创建其他构造函数。这些是复制构造函数,在C++11及更新版本的情况下,还有移动构造函数。

当您看到负计数时,可以通过使用编译器生成的构造函数之一来解释。因此,实例数量增加,但liveGoombas没有。

为了获得准确的计数,你应该将Goomba更改为如下所示。

如果您正在使用启用了移动语义的编译器(C++0x/C++11及更新版本):

class Goomba
{
public:
  static int liveGoombas;
  Goomba() { liveGoombas++; }
  Goomba(const Goomba&) { liveGoombas++; }
  Goomba(Goomba&&) { liveGoombas++; }
  // Need to explicitly state we want default or it will be deleted
  // due to the above move constructor having been defined.
  Goomba & operator = (const Goomba&) = default;
  // Not really essential but including for completeness 
  Goomba & operator = (const Goomba&&) = default;
  ~Goomba() { liveGoombas--; }
};

否则:

class Goomba
{
public:
  static int liveGoombas;
  Goomba() { liveGoombas++; }
  Goomba(const Goomba&) { liveGoombas++; }
  ~Goomba() { liveGoombas--; }
};

默认的赋值操作符很好,因为它们不创建新实例,只修改现有实例。因此,他们不应该更改实例计数。

当将值Goomba传递给正在静默复制的函数时,此外,您需要使用pushback操作和向量偶尔进行的可能的重新定位来复制goombas。

为了查看(在代码中)您隐式使用这些函数的位置(默认构造的复制和赋值运算符),将它们定义为私有的且没有实现,编译器将在引用需要这些运算符的行时出错。

您可能想要沿着构造函数的行给出复制构造函数和赋值的实现。

C++11

如果定义移动构造函数:

Goomba(const Goomba&&) { liveGoombas++; }

你最好让编译器为你生成这两个:

Goomba& operator=(const Goomba&&) = default; 
Goomba& operator=(const Goomba&) = default; 

否则它们将被自动删除。

如果你没有定义移动构造函数,赋值不会被删除,但明确声明默认赋值对你来说仍然是个好主意:

Goomba& operator=(const Goomba&) = default; 

其他答案都不完全准确。

首先,不要将move构造函数声明为接受const参数,这是毫无意义的,也不是惯用的,因为您最多可以具有与副本相同的性能;此外,移动意味着对"移动自"对象的修改。

其次,如果您执行以下操作,则Goomba(Goomba&&) { liveGoombas++; }会中断:

Goomba g;
Goomba b(std::move(g));
std::cout << Goomba::liveGoombas << std::endl; // prints 2, is that really correct?

这应该可以为单线程程序(此处为实时版本)提供技巧:

class Goomba
{
  bool alive;
public:
  static int liveGoombas;
  Goomba()
    : alive(true)
  { liveGoombas++; }
  Goomba(const Goomba& rhs)
    : alive(rhs.alive)
  {
    if (alive)
      liveGoombas++;
  }
  // Move constructor
  Goomba(Goomba&& rhs)
    : alive(std::move(rhs.alive))
  {
    if (alive)
      liveGoombas++;
    rhs.kill(); // modifies moved from object!
  }
  Goomba& operator =(const Goomba& rhs)
  {
    kill();
    alive = rhs.alive;
    if (alive)
      liveGoombas++;
    return *this;
  }
  // Move assignment
  Goomba& operator =(Goomba&& rhs)
  {
    kill();
    alive = std::move(rhs.alive);
    if (alive)
      liveGoombas++;
    rhs.kill(); // modifies moved from object!
    return *this;
  }
  ~Goomba() {
    kill();
  }
private:
  void kill() {
    if (alive) {
      liveGoombas--;
      alive = false;
      // insert other death/dying code here
    }
  }
};

注意:通常会将移出的对象视为"已销毁",尽管事实并非如此。