我在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();
}
现在情况似乎正常。但我仍然没有弄清楚究竟是什么问题。
答案 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;
问题是你正在向量中获取本地对象的地址。当向量需要更多空间时,它会重新分配内存,因此Class2
和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 ( 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::c2v
,Class2
是一个未知的符号。如果我加
前向声明(以及noi
的定义),它仍然包含
未定义的行为,并且不使用g ++进行编译,至少不使用
通常的选择。用a实例化标准模板是违法的
不完全类型。由于您的类型具有循环依赖性,因此您就是
将不得不在某处使用指针。
此外,Class2
和Class3
时的指针会发生什么变化
对象被复制(std::vector
将复制)。从评论你
made(Class2
代表供应商,Class3
代表优惠),
这些类应该是不可复制的。这很大程度上取决于
设计,但在大多数情况下,实体类,它模拟问题
域名,应该是不可复制的。这也导致使用指针
他们,而不是副本。 (引用语义,而不是值
语义。)
当然,使用指针确实会引入对象生命周期的问题。 您必须决定供应商和要约的时间和方式 存在,何时它们不复存在。这应该是你的一部分 设计。
答案 4 :(得分:0)
尽管矢量内容的重新定位以及Class2
或Class3
个对象的复制不应该产生任何问题(与其他答案可能存在的情况相反),但是当您创建一个问题时会出现问题Class1
的本地对象并将其复制。当您现在尝试通过其中一个Class1
指针访问原始instance
对象时,此对象可能已经被销毁,因此对其中一个成员的访问会导致段错误。