【问题标题】:Why is the pointer in this nested class missing?为什么这个嵌套类中的指针丢失了?
【发布时间】:2018-05-21 19:40:49
【问题描述】:

简而言之:

B 类有一个指向 C 类的指针,C 类有一个 D 类有一个指向 B 类的指针

通过复制将 B 类分配给 A 类中的数组,期望看到 ptr 指向数组中的新实例而不是原始实例,但失败了。

我已经能够做一些解决方法,但我想知道为什么我原来的方法会失败。

更详细的解释如下,重现问题的代码也贴出来

感谢任何能够解释发生了什么的人。

有 6 个类:

class CastInfo //包含一个字符*

class Skill //抽象类,包含 CastInfo

类运动:公共技能

类 Move1:公共运动

class Character //包含一个Movement*,实际上是Move1*

class Squad //包含一个字符数组

具有以下关系:

  1. CastInfo 中的Character* 应该指向拥有技能的角色 是 CastInfo 的所有者

  2. 将技能分配给角色时,CastInfo 中的角色* 指向该角色

  3. Squad 数组中的 Character 应该被复制,所以会有 2 个实例,并且 CastInfo 中的 Character* 也应该指向 Squad 数组中的 Character,而不是原始实例

预期结果是:

  1. move1 != ch1.move1 != square.ch[0].move1(这已经满足了)

  2. ch1.move1->cast_info.caster == &ch1 != square.ch[0].move1->caster_info.caster(这是问题所在)

输出有2个案例(试过):

在 Squad 的构造函数中: 如果使用

characters_[i] = characters[i];

角色被正确复制,但技能在同一地址

move1: 00000270E6093500
ch1: 000000BC6DCFF378
ch1.move1: 00000270E6093E60
ch1.move1->cast_info.caster: 000000BC6DCFF378
squad.ch[0]: 000000BC6DCFF3E0
squad.ch[0].move1: 00000270E6093E60
squad.ch[0].move1->cast_info.caster: 000000BC6DCFF378

如果使用

characters_[i] = Character(characters[i]);

角色被正确复制,但技能缺失(指向某个奇怪的位置)

move1: 00000230FDCEF080
ch1: 00000058A11DF548
ch1.move1: 00000230FDCEF260
ch1.move1->cast_info.caster: 00000058A11DF548
squad.ch[0]: 00000058A11DF5B0
squad.ch[0].move1: 00000230FDCEF0E0
squad.ch[0].move1->cast_info.caster: 00000058A11DF378

第一种情况,我猜可能是因为我没有重载operator=,所以只复制了地址。我试图重载它,但它导致了更多问题。 (比如使用 Builder.Build() 时)

在第二种情况下,我希望它首先调用复制构造函数,它触发 SetMove1(),它调用 SetCaster()。 move1 如图所示被克隆,但我不明白为什么 caster 没有正确更新。 (虽然是在构造之后调用operator=,但地址应该保持不变。)

以下代码应该会重现该问题:

运动.h

#pragma once
class Character;

struct CastInfo
{
    Character* caster;
    int coeff;
};

class Skill
{
public:
    CastInfo cast_info;
    Skill() {};
    ~Skill() {};
    virtual void DoSomething() = 0;
};

class Movement : public Skill
{
public:
    Movement();
    ~Movement();
    virtual void DoSomething() { ; }
    virtual Movement* Clone() const { return new Movement(*this); }
};

class Move1 : public Movement
{
public:
    Move1() { cast_info.coeff = 123; }
    void DoSomething() { ; }
    virtual Move1* Clone() const { return new Move1(*this); }
};

class Move2 : public Movement
{
public:
    void DoSomething() { ; }
};

motion.cpp:

#include "motion.h"
Movement::Movement() { }
Movement::~Movement() { }

test.h:

#pragma once
#include <string>
#include <vector>
#include "motion.h"
#define SQUAD_SIZE 6

extern Movement* null_movement;
class Character
{
public:
    class Builder;
    Character();
    ~Character();
    Character(const Character& character);
    Character& SetMove1(Movement* skill);
public:
    int id_;
    Movement* move1_ = null_movement;
    Movement* move2_ = null_movement;
    Character(int id) : id_(id) { ; }
    void SetCaster();
};

class Character::Builder : public Character
{
public:
    Builder& SetId(int i) { id_ = i; return *this; }
    Character Build() { return Character(id_); }
};

class Squad
{
public: 
    class Builder;
    Squad() { }
    Squad(const Squad& squad);
    ~Squad() { }
public:
    Character characters_[SQUAD_SIZE];
    Squad(Character* characters);
};

class Squad::Builder :public Squad
{
public:
    Builder& SetCharacter(const Character& character, const int position) { characters_[position] = character; return *this; }
    Squad Build() { return Squad(characters_); }
};

test.cpp

#include <iostream>
#include "test.h"

Movement* null_movement = new Move2();
Character::Character() : id_(0) { }
Character::~Character() {}
Character::Character(const Character& character) {
    id_ = character.id_;
    SetMove1(character.move1_);
}

Character& Character::SetMove1(Movement* move1) {
    if (!move1) return *this;
    move1_ = move1->Clone(); 
    SetCaster();
    return *this;
}

void Character::SetCaster() { 
    if (move1_ != NULL) move1_->cast_info.caster = this;
}

Squad::Squad(const Squad& squad) {
    *this = squad;
}

Squad::Squad(Character* characters) {
    for (int i = 0; i < SQUAD_SIZE; i++) {
        //characters_[i] = characters[i];  //character copied, skill same address
        characters_[i] = Character(characters[i]); //character copied, skill missing
    }
}

main.cpp

#include <iostream>
#include "test.h"
#include "motion.h"
int main() {

    Move1* move1 = new Move1();
    std::cout << "move1: " << move1 << std::endl;

    Character ch1 = Character::Builder().SetId(1).Build();
    Character ch2 = Character::Builder().SetId(2).Build();
    ch1.SetMove1(move1);

    std::cout << "ch1: " << &ch1 << std::endl;
    std::cout << "ch1.move1: " << (ch1.move1_) << std::endl;
    std::cout << "ch1.move1->cast_info.caster: " << (ch1.move1_->cast_info.caster) << std::endl;

    Squad squad = Squad::Builder().SetCharacter(ch1, 0).SetCharacter(ch2, 1).Build();

    std::cout << "squad.ch[0]: " << &(squad.characters_[0]) << std::endl;
    std::cout << "squad.ch[0].move1: " << (squad.characters_[0].move1_) << std::endl;
    std::cout << "squad.ch[0].move1->cast_info.caster: " << (squad.characters_[0].move1_->cast_info.caster) << std::endl;

    system("PAUSE");
    return 0;
}

如前所述,我有一个解决方法来实现我的目标:

通过创建另一个方法,遍历 Squad 中的数组,并调用每个角色的 SetCaster() 方法。

void Squad::SetCaster() {
    for (int i = 0; i < SQUAD_SIZE; i++) {
        characters_[i].SetCaster();
    }
}

但我认为这很脏,因为每次在 Builder::Builder() 之后,都必须调用 SetCaster(),这是不直观且容易出错的。

【问题讨论】:

  • 您是否有机会将其缩减为更易于管理但仍能证明问题的东西?
  • 说真的:Character ch1 = Character::Builder().SetId(1).Build(); 这应该比Character ch1(1); 更容易阅读
  • copy constructor, which triggers SetMove1()。如果编译器认为它可以进行优化,则可以省略复制构造函数(即使复制构造函数有副作用(它不应该))。
  • @Steve 我相信这里列出的所有成员和方法都已使用,除了类 Skill 和类 Move2,它们显示为表达技能系统的样子。
  • @LokiAstari 真的吗?我不知道编译器有没有可能通过它。但是,在这种情况下,它似乎没有通过,因为我在复制构造函数中设置了一个断点并且它正在工作。有什么关键词可以了解更多吗?

标签: c++ class pointers nested operator-overloading


【解决方案1】:

我想我找到了问题所在,如下图:

问题出在

Squad::Squad(Character* characters) {
    for (int i = 0; i < SQUAD_SIZE; i++) {
        //characters_[i] = characters[i];  //character copied, skill same address
        characters_[i] = Character(characters[i]); //character copied, skill missing
    }
}

如问题所述,使用注释行只是复制值,这是不正确的。

什么

characters_[i] = Character(characters[i]); //character copied, skill missing

如下:

  1. 创建一个Character,通过调用Character的构造函数,这个对象在地址A。调用SetMove1(),调用SetCaster()。 cast_info 中的指针正确指向 A。

  2. 将对象分配给characters_[i],其地址位于地址B,因为characters_的地址是在创建小队时分配的。由于我没有重载 Character::operator=,所以指针仍然指向 address A

  3. 构造完成,小队返回。

这就是原因

std::cout << "squad.ch[0].move1->cast_info.caster: " << (squad.characters_[0].move1_->cast_info.caster) << std::endl;

显示第三个地址(地址 A),它既不是 &(character[0])(地址 B)也不是 &ch1(原始字符的地址)

解决方案是重载 operator= 或将我的“解决方法” (Squad::SetCaster()) 放在 for 循环之后的构造函数中。

如果有什么不对的地方请指正,或者有更好的解决办法。

【讨论】:

  • 顺便说一句,重载 operator= 似乎与复制构造函数完全相同。有什么办法可以避免重复代码两次?互相调用似乎是一团糟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-13
  • 2021-01-16
  • 2011-04-09
相关资源
最近更新 更多