C ++在类构造函数中填充向量

时间:2011-09-30 09:12:16

标签: c++ vector nested instantiation

我在Class3的对象内部遇到麻烦的问题,请看这个简化的类:

class Class1 {
public:
    std::vector<Class2> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};

他们的建设者:

Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( Class3 ( this->instance, k ) );
    }
}

在main()中,Class1的对象使用其默认构造函数进行实例化。因此,它会创建一个向量c2v,并用Class2的'noi'对象填充它。

同时,当Class2的对象被放入c2v向量时,它们被实例化,每个对象创建一个向量c3v,并用Class3的“非”对象填充它。

代码编译很好但是在运行时从Class3对象访问Class2的公共属性(通过this->instance->c2v[0].getSomeAttribute())时,程序会以EXC_BAD_ACCESS停止。

使用调试器进行检查显示指向c2v[0]的指针已损坏(其值变为0x0)。

我是C ++的新手我想知道尝试以这种方式实例化向量时的错误是什么。我应该只声明向量并在创建Class2和Class3的所有实例完成后调用的单独函数中填充它们吗?

我正在添加一些实际的代码,希望它不会太长时间阅读(请理解我省略了一些前向声明和预处理器指令):

// global variables
extern char *filename; //not used
extern int nodes;
extern int numProdotti;
extern int classe; //not used
extern int maxNumRange; //not used
extern int *numRanges;
extern int *list ;
extern int *typeProdMarket;
extern int totalQtyDemand; //not used
extern int totNumRanges; //not used

extern struct product {
    int     qty;
    int     cost;
} **prodMarket;

extern struct range {
    int     discount;
    int     startOfRange;
} **rangeMarket; //not used

int main ( int argc, char *argv[] )
{
    Ctqd instance;
    instance.runOpt();
}

class Ctqd {
    void greedySimple();
    void greedySimpleReverse();
    void greedyFromY();
    void greedyByNiceness1();
    void greedyByNiceness2();
    void greedyByStepTier1();
    void greedyByStepTier2();
    void randomLocalSearch1();
    void LocalSearch2opt();
public:
    std::vector<Item> items;
    std::vector<Supplier> suppliers;
    Solution *solution;
    Ctqd();
    ~Ctqd();
    void runOpt();
};

class Supplier {
    Ctqd *instance;
    int id;
    int refTotQty;
    int refDiscount;
    int refDiscountTier;
    double refTotPrice;
    double refUnitPrice;
    double niceness;
    int purTotQty;
    int purDiscount;
    int purDiscountTier;
public:
    std::vector<Offer> offers;
    Supplier ( Ctqd *instance, int id );
    int getId();
    int getRefTotQty();
    int getRefDiscount();
    int getRefDiscountTier();
    double getRefTotPrice();
    double getRefUnitPrice();
    double getNiceness();
    int getPurTotQty();
    int getPurDiscount();
    int getPurDiscountTier();
    void updateStats();
};

class Offer {
    Supplier *supplier;
    int id;
    int refQty;
    double refPrice;
    double niceness;
    int purQty;
public:
    Offer ( Supplier *supplier, int id );
    int getId();
    int getRefQty();
    double getRefPrice();
    double getNiceness();
    int getPurQty();
    void setPurQty ( int qty );
    int remainingQty();
};

Ctqd::Ctqd()
{
    // constructing items vector
    for ( int k = 0; k < numProdotti; ++k ) {
        items.push_back ( Item ( this, k ) );
    }

    // constructing suppliers vector
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) );
    }

    // building solution
    solution = new Solution ( this );
}

Supplier::Supplier ( Ctqd *instance, int id )
{
    this->instance = instance;
    this->id = id;
    // computing total reference quantity
    refTotQty = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotQty += std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    // computing reference discount coefficients
    refDiscount = 0;
    refDiscountTier = 0;

    for ( int r = 0; r < numRanges[ this->id ]; ++r ) {
        if ( refTotQty < rangeMarket[ this->id ][ r ].startOfRange ) {
            break;
        }
        else {
            refDiscount = rangeMarket[ this->id ][ r ].discount;
            refDiscountTier = r;
        }
    }

    //computing total reference price
    refTotPrice = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotPrice += prodMarket[ this->id ][ k ].cost * std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    refTotPrice = refTotPrice * ( 1.000 - refDiscount / 100.000 );
    //computing reference unit price
    refUnitPrice = refTotPrice / refTotQty;
    //computing supplier niceness
    niceness = refTotQty / refUnitPrice;
    purTotQty = 0;
    purDiscount = 0;
    purDiscountTier = 0;

    // building offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) );
    }
}

Offer::Offer ( Supplier *supplier, int id )
{
    this->supplier = supplier;
    this->id = id;
    // computing reference quantity
    refQty = std::min ( list[ this->id ] , prodMarket[ this->supplier->getId() ][ this->id ].qty );
    // computing reference price
    refPrice = prodMarket[ this->supplier->getId() ][ this->id ].cost * ( 1.000 - this->supplier->getRefDiscount() / 100.000 );
    // computing niceness of the offer
    niceness = refQty / ( ( prodMarket[ this->supplier->getId() ][ this->id ].cost + refPrice ) / 2 );
    // init purQty to 0
    purQty = 0;
}

这是我获得EXC_BAD_ACCESS的地方:

int Offer::remainingQty()
{
    return prodMarket[ supplier->getId() ][ id ].qty - purQty;
}

我做了一些实验: 将Ctqd类和Supplier类中的向量更改为指向对象的指针向量。 问题只是部分解决了。 在构建商品对象时仍然有EXC_BAD_ACCESS。

Offer类的构造函数需要来自创建它的Supplier对象的数据。 我认为在供应商矢量仍在填充时访问该数据可能会导致问题,因此我在供应商中创建了一个初始化()函数:

void Supplier::initialize()
{
    // constructing offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( new Offer ( this->instance, id, k ) );
    }
}

并在Ctqd类构造函数的末尾添加了这个:

// init newly built objects
for ( int i = 0; i < nodes; ++i ) {
    suppliers[i]->initialize();
}

现在情况似乎正常。但我仍然没有弄清楚究竟是什么问题。

5 个答案:

答案 0 :(得分:1)

到目前为止,最简单(也是最好)的解决方法是使用std::deque代替std::vector。你不需要使用指针;你可以坚持原来推动物体的计划。

使用双端队列,push_back保证不会使对其元素的引用无效。实际上push_front也是如此。它仍然支持恒定时间随机访问(foo[n])。

当逐步构建一个装满对象的容器时,通常会出现deque。

作为一般规则,std::deque可能是您从未使用过的最佳数据结构。

答案 1 :(得分:0)

您不遵守3的规则。您应该为所有类实现析构函数,复制构造函数和赋值运算符。

考虑一下:

c2v.push_back ( Class2 ( this, i ) );

这将创建一个临时对象,复制它并将其放入向量中。副本和临时对象将通过实例成员指向同一位置。但是,当临时对象被销毁时(在下一行之前),该内存被释放并可供使用。所以现在你的向量内部会有一个Class2类型的对象,其中包含一个无效字段。

这只是我的猜测。如果没有帮助,请将代码发布在main中。

答案 2 :(得分:0)

更改

std::vector<Class2> c2v;

std::vector<Class2 *> c2v;

std::vector<Class3> c3v;

std::vector<Class3*> c3v;

问题是你正在向量中获取本地对象的地址。当向量需要更多空间时,它会重新分配内存,因此Class2Class3对象的内容会发生变化。

class Class1 {
public:
    std::vector<Class2 *> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3 *> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};


Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( new Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( new Class3 ( this->instance, k ) );
    }
}

不要忘记在Class1'和Class2'析构函数中进行清理。

编辑: 为什么会出现这种奇怪的行为:

第1步

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- Supplier's constructor is invoked here
                                                // to construct automatic object of Supplier class
    }
}

步骤2.我们在自动对象的构造函数内部:

Supplier::Supplier ( Ctqd *instance, int id )
{
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) ); // consider that we are passing this to 
                                               // Offer's constructor. this is a pointer to
                                              // automatic variable
    }
}

步骤3.返回步骤1.但现在我们已创建自动对象供应商

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- automatic object of class Supplier
                                                     // is created. Now as long as push_back() taking
                                                    // parameter by value yo are passing copy of
                                                   // automatic object to push_back();
                                                  // as long as you didn't define copy constructor
                                                 // for Supplier compiler adds default for you.
    }
}

步骤4.自动对象的副本将保存到suppliers向量。新对象(自动对象的副本)具有向量offers,其值与我们的自动对象具有完全相同的值(thx到向量的复制构造函数)。所以每个对象都有成员supplier指向...(geass what :))正确!他们都指向自动化对象。

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- magic here
    }
}

步骤5.自动对象已被破坏(记住你如何将指向此对象的指针传递给Offer的构造函数?)。哎呀你说。

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); 
                                                      // <-- automatic doesn't exist no more
    }
}

那么如果供应商的某个方法试图通过Supplier成员访问supplier对象会发生什么呢(我们,聪明的孩子,知道点死对象......太伤心了... QQ)。猜测。我相信它会因为意识到他一生中第一次看到死亡物而死亡。)。

答案 3 :(得分:0)

对于初学者,你的代码将无法编译:当编译器遇到时 声明Class1::c2vClass2是一个未知的符号。如果我加 前向声明(以及noi的定义),它仍然包含 未定义的行为,并且不使用g ++进行编译,至少不使用 通常的选择。用a实例化标准模板是违法的 不完全类型。由于您的类型具有循环依赖性,因此您就是 将不得不在某处使用指针。

此外,Class2Class3时的指针会发生什么变化 对象被复制(std::vector将复制)。从评论你 made(Class2代表供应商,Class3代表优惠), 这些类应该是不可复制的。这很大程度上取决于 设计,但在大多数情况下,实体类,它模拟问题 域名,应该是不可复制的。这也导致使用指针 他们,而不是副本。 (引用语义,而不是值 语义。)

当然,使用指针确实会引入对象生命周期的问题。 您必须决定供应商和要约的时间和方式 存在,何时它们不复存在。这应该是你的一部分 设计。

答案 4 :(得分:0)

尽管矢量内容的重新定位以及Class2Class3个对象的复制不应该产生任何问题(与其他答案可能存在的情况相反),但是当您创建一个问题时会出现问题Class1的本地对象并将其复制。当您现在尝试通过其中一个Class1指针访问原始instance对象时,此对象可能已经被销毁,因此对其中一个成员的访问会导致段错误。