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

Posted

技术标签:

【中文标题】C++ 在类构造函数中填充向量【英文标题】:C++ populating vectors inside class constructors 【发布时间】:2011-09-30 09:12:16 【问题描述】:

我在 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 对象(通过this-&gt;instance-&gt;c2v[0].getSomeAttribute())访问 Class2 的公共属性时,程序会以 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 类中的向量更改为指向对象的向量。 问题仅部分解决。 构造 offer 对象时仍然有 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();

现在似乎一切正常。但是我还是没有弄清楚到底是什么问题。

【问题讨论】:

真正想做什么?指针所有权的政策是什么? Class1 只是问题实例的容器。 Class2 代表我可以从中购买商品的供应商。 Class3 代表供应商对商品的报价。因此,我将所有供应商放在数组 c2v 中,每个供应商创建自己的 c3v 数组并用它的报价填充它。指针所有权都是公开的。 这个例子对我有用。您应该发布一个给出错误的完整可编译示例。 您可能正在复制Class1 的实例,而没有更新包含的Class2Class3 实例中的实例指针。 显示导致错误的实际代码。 【参考方案1】:

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

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

在逐步构建一个装满对象的容器时,通常需要双端队列。

一般来说,std::deque 可能是您从未使用过的最佳数据结构。

【讨论】:

这个想法+1。但是,您并没有解释为什么它实际上会失败。我对为什么它失败而不是找到解决方法更感兴趣。 @LuchianGrigore 我们没有足够的代码来确定它失败的原因,但它失败是一个相当好的猜测,因为某些对象中保存的指针指向已“移动”的对象“ 某种程度上来说。 std::deque 如果所有插入都在末端,则不会移动对象,所以它可能会有所帮助,但我宁愿怀疑如果他的程序正在模拟某些东西,他会从某处的向量中间删除对象,所以即使 @ 987654328@ 不起作用。 提醒一下,即使使用std::deque,代码也将是未定义的,并且将无法使用某些编译器进行编译。你必须在某处打破循环引用,使用指针而不是对象。 @James:你能解释一下“代码将未定义”是什么意思吗?哪个方面? §17.4.3.6/2:“特别是,在以下情况下效果未定义:[...]--如果使用不完整类型 (3.9) 作为模板参数时实例化模板组件。”在实践中,您通常会得到一个编译器错误或者代码可以工作:在这种情况下,您会从 g++ 中得到一个编译器错误,至少在我通常使用的选项中是这样。 (我认为是-D_GLIBCXX_CONCEPT_CHECKS 触发了这里的错误。)委员会想让它成为一个可诊断的错误,但这需要概念,但没有成功。【参考方案2】:

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

考虑这条线:

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

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

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

【讨论】:

哪个字段无效? 三法则在这里似乎不是问题。 我不相信这是正确的——我认为问题出在Class1 的(编译器生成的)复制构造函数上——所有子对象都将指向原始的Class1 而不是副本. 除非他给我们完整的、可编译的、运行的代码,否则我们只能推测,对吧? 我想发布完整的代码,但它相当大,其中大部分对这个问题毫无用处。如果你不介意我会做的。【参考方案3】:

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

另外,Class2Class3 中的指针在 对象被复制(并且std::vector 将被复制)。从你的评论 制造(Class2 代表供应商,Class3 代表报价), 这些类可能应该是不可复制的。这在很大程度上取决于 设计,但在大多数情况下,实体类对问题进行建模 域,应该是不可复制的。这也导致使用指针 他们,而不是副本。 (引用语义,而不是值 语义。)

当然,使用指针确实会引入对象生命周期的问题。 您必须决定供应商和报价何时以及如何进入 存在,以及它们何时停止存在。这应该是您的一部分 设计。

【讨论】:

很抱歉没有包括前向声明。您实际上是对的,使用这些类,我试图从实体关系图中建模我的问题。您是否建议我应该将这些对象向量转换为指向对象的指针向量?如果可能有帮助,我会在主帖中添加更多代码。 @MarcoRomano 是的。首先,如果所讨论的对象是实体对象,它们可能无论如何都应该被动态分配,因为通常它们的生命周期不对应于任何逻辑程序范围。通常,它们不应该是可复制的,尽管这更多地取决于上下文。最后,不管语义如何,你真的别无选择;在不完整的类型上实例化 std::vector 是未定义的行为,因此前向声明是不够的;除了使用指针之外没有其他合法的解决方案。 我对否决票很好奇,因为我是唯一一个指出没有指针的代码具有未定义行为这一事实的人。 (我注意到其他提出建议的人也投了反对票,尽管没有其他法律解决方案。) 我没有投反对票(我是 stackvoerflow 菜鸟:问题作者是唯一能够投票回答的人吗?) @MarcoRomano 我没说你做过。我故意将评论放在单独的评论中,以便更普遍地解决它。 (我承认我不理解这里的投票。这似乎相当武断,有时回答不好会得到很多选票,而真正好的答案有时会没有投票,甚至是否定的。)【参考方案4】:

虽然向量内容的重定位和Class2Class3 对象的复制不应该产生任何问题(与其他答案可能说明的相反),但当您创建@ 的本地对象时会出现问题987654323@ 并复制它。当您现在尝试通过instance 指针之一访问原始Class1 对象时,该对象可能已经被销毁,因此对其成员之一的访问会导致段错误。

【讨论】:

其实class1是一种“母对象”。它只从 main 实例化一次并且永远不会被销毁(它在程序结束时结束)。也许我不应该让 Ctqd 成为一个类,而只是一个包含静态函数和属性的命名空间? (对不起,可能不正确的术语,我以前只使用过 Java。) @MarcoRomano 视情况而定,但通常情况下,您不需要像在 Java 中那样在 C++ 中使用单个“应用程序”对象。 (另一方面,有时使用一个比使用大量全局数据更好。)【参考方案5】:

改变

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' 析构函数。

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

第一步

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 与我们的自动对象的值完全相同(感谢向量的复制构造函数)。所以每个对象都有成员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)。猜测。我相信它会因为意识到他有生以来第一次看到死物而死;)。

【讨论】:

他拿的不是地址而是价值。在您更改后,他将获取地址(如指针)... @LuchianGrigore 他没有获取地址,所以他最终复制了不可复制的对象。他应该获取地址,并且对象应该是动态分配的。 您说的是使用指向对象的指针向量而不是对象向量,对吧? @Marco Romano 正确。因为,正如上面有人提到的那样,对象可能因此被删除或复制——改变了它们的位置。因此,当您引用不再属于该对象的内存地址时 - 您的行为未定义。在最坏的情况下,如果您所指的内存段与虚拟地址空间分离,则会出现分段错误。所以这里的指针 - 是唯一正确的方法。只要记住释放他们! 看来我的问题与此有关? ***.com/q/580053/972721

以上是关于C++ 在类构造函数中填充向量的主要内容,如果未能解决你的问题,请参考以下文章

在类构造函数上初始化向量

运行时在构造函数中初始化向量类成员——C++

C++学习 之 类中的特殊函数和this指针

如何在 C++ 中使用构造函数初始化二维向量?

c++向量构造函数实例化冲突

我们啥时候需要 C++ 中的私有构造函数?