我不是编程的新手,但是在使用Java之后我又回到了C ++,并且对于不是指针的类变量感到有些困惑。给出以下代码:
#include <iostream>
#include <map>
using namespace std;
class Foo {
public:
Foo() {
bars[0] = new Bar;
bars[0]->id = 5;
}
~Foo() { }
struct Bar {
int id;
};
void set_bars(map<int,Bar*>& b) {
bars = b;
}
void hello() {
cout << bars[0]->id << endl;
}
protected:
map<int,Bar*> bars;
};
int main() {
Foo foo;
foo.hello();
map<int,Foo::Bar*> testbars;
testbars[0] = new Foo::Bar;
testbars[0]->id = 10;
foo.set_bars(testbars);
foo.hello();
return(0);
}
我得到的预期输出为5&amp; 10.但是,我对C ++中的引用和指针缺乏了解,这让我想知道这实际上是否会在野外工作,或者如果一旦testbars超出范围就会barf。当然,在这里,testbars在程序结束之前不会超出范围,但如果它是在另一个类函数中创建的函数变量怎么办?无论如何,我想我的主要问题是,将条形变量创建为指向地图的指针会更好/更安全吗?
答案 0 :(得分:4)
无论如何,我想我的主要问题是 我创造它会更好/更安全吗? bars类变量作为指针 到地图?
没有。 C ++在这方面与Java完全不同,可能还有其他方面。如果你发现自己使用指针并为它们分配新的对象,你可能做错了什么。为了学习正确的做事方式,我建议您获取Koenig&amp;撰写的Accelerated C++副本。 MOO,
答案 1 :(得分:3)
成员变量bars
是“类字典”/关联数组类的单独实例。因此,当它在set_bars中分配时,参数b
的内容将被复制到bars
中。因此,无需担心foo
和testbars
的相对生命周期,因为它们是独立的“类似价值”的实体。
Bar对象的生命周期存在更多问题,目前这些对象永远不会被删除。如果您在某处添加代码以删除它们,那么您将引入另一个问题,因为您正在复制Bar对象的地址(而不是对象本身),因此您有两个不同地图指向的相同对象。删除对象后,另一个地图将继续引用它。这是你应该像C ++中的瘟疫一样避免的事情!分配给new
的对象的裸指针是等待发生的灾难。
引用(用&amp;声明)与关于对象生存期的指针没有区别。为了允许您从两个地方引用同一个对象,您可以使用指针或引用,但这仍然会给您带来解除分配的问题。
您可以通过使用类似shared_ptr
的类来解决解除分配问题,这应该包含在任何最新的C ++环境中(std::tr1
中)。但是你可能遇到周期性指针网络的问题(例如,A指向B和B指向A),不会自动清理。
答案 2 :(得分:1)
对于每个新的你需要相应的删除。 如果你在调用delete之后尝试引用内存 - 那就是 - 那么程序确实会“barf”。
如果你不那么你会没事的就是这么简单。
您应该设计您的类,以便内存的所有权是明确的,并且您知道对于您正在进行相同释放的每个分配。 永远不要假设另一个类/容器会删除你分配的内存。
希望这有帮助。
答案 3 :(得分:0)
在下面的代码中,您可以传递Bars的地图,然后就可以修改该类之外的Bars。
但是。但除非你再次调用set_bars。
当一个对象负责创建和删除Bars时更好。在你的情况下,这不是真的。
如果你想要,你可以使用boost :: shared_ptr&lt;酒吧&gt;而不是酒吧*。这将是更像Java的行为。
class Foo {
public:
Foo() {
bars[0] = new Bar;
bars[0]->id = 5;
}
~Foo() { freeBarsMemory(); }
struct Bar {
int id;
};
typedef std::map<int,Bar*> BarsList;
void set_bars(const BarsList& b) {
freeBarsMemory();
bars = b;
}
void hello() {
std::cout << bars[0]->id << std::endl;
}
protected:
BarsList bars;
void freeBarsMemory()
{
BarsList::const_iterator it = bars.begin();
BarsList::const_iterator end = bars.end();
for (; it != end; ++it)
delete it->second;
bars.clear();
}
};
答案 4 :(得分:0)
我不是编程的新手,但是在使用Java之后我又回到了C ++,对于那些不是指针的类变量感到有些困惑。
混淆似乎来自堆上的数据和不一定在堆上的数据的组合。这是造成混淆的常见原因。
在您发布的代码中,bars
不是指针。因为它在类范围内,所以它将一直存在,直到包含它的对象(testbars
)被销毁。在这种情况下,testbars
在堆栈上创建,因此当它超出范围时将被销毁,无论该范围的嵌套程度如何。当testbars
被销毁时,testbars
的子对象(无论它们是父类还是testbars
对象中包含的对象)将使它们的析构函数以明确定义的顺序在该精确时刻运行
这是C ++的一个非常强大的方面。想象一个具有10行构造函数的类,它打开网络连接,在堆上分配内存,并将数据写入文件。想象一下,类的析构函数撤消所有这些(关闭网络连接,释放堆上的内存,关闭文件等)。现在想象一下,在构造函数的中途创建此类的对象失败(例如,网络连接已关闭)。程序如何知道析构函数的哪些行将撤消成功构造函数的各个部分?没有一般方法可以知道这一点,因此该对象的析构函数不会运行。
现在想象一个包含十个对象的类,每个对象的构造函数都会做一件必须回滚的事情(打开网络连接,在堆上分配内存,将数据写入文件等)和每个对象的析构函数包括回滚操作所需的代码(关闭网络连接,释放对象,关闭文件等)。如果只成功创建了五个对象,则只需要销毁这五个对象,并且它们的析构函数将在该确切时刻运行。
如果已在堆上创建testbars
(通过new
),那么只有在调用delete
时才会销毁它。通常,在堆栈上使用对象要容易得多,除非有一些理由使对象比它创建的范围更长。
带我到Foo::bar
。 Foo::bars
是引用堆上对象的map
。好吧,它指的是在这个代码示例中引用堆上分配的对象的指针(指针也可以引用堆栈上分配的对象)。在示例中,您发布了这些指针所引用的对象永远不会delete
d,并且因为这些对象在堆上,所以会出现(小)内存泄漏(操作系统在程序退出时清除)。根据STL,std::maps
类Foo::bar
执行不 delete
指针,它们在被销毁时引用。 Boost有一些解决这个问题的方法。在你的情况下,简单地不在堆上分配这些对象可能是最容易的:
#include <iostream>
#include <map>
using std::map;
using std::cout;
class Foo {
public:
Foo() {
// normally you wouldn't use the parenthesis on the next line
// but we're creating an object without a name, so we need them
bars[0] = Bar();
bars[0].id = 5;
}
~Foo() { }
struct Bar {
int id;
};
void set_bars(map<int,Bar>& b) {
bars = b;
}
void hello() {
cout << bars[0].id << endl;
}
protected:
map<int,Bar> bars;
};
int main() {
Foo foo;
foo.hello();
map<int,Foo::Bar> testbars;
// create another nameless object
testbars[0] = Foo::Bar();
testbars[0].id = 10;
foo.set_bars(testbars);
foo.hello();
return 0;
}