指针和参考问题
Posted
技术标签:
【中文标题】指针和参考问题【英文标题】:pointers and references question 【发布时间】:2009-10-09 13:30:20 【问题描述】:#ifndef DELETE
#define DELETE(var) delete var, var = NULL
#endif
using namespace std;
class Teste
private:
Teste *_Z;
public:
Teste()
AnyNum = 5;
_Z = NULL;
~Teste()
if (_Z != NULL)
DELETE(_Z);
Teste *Z()
_Z = new Teste;
return _Z;
void Z(Teste *value)
value->AnyNum = 100;
*_Z = *value;
int AnyNum;
;
int main(int argc, char *argv[])
Teste *b = new Teste, *a;
a = b->Z();
cout << "a->AnyNum: " << a->AnyNum << "\n";
b->Z(new Teste);
cout << "a->AnyNum: " << a->AnyNum << "\n";
//wdDELETE(a);
DELETE(b);
return 0;
我想知道这段代码是否存在内存泄漏
它工作正常,*a
设置了两次,AnyNum
在每个cout <<
上打印不同的数字
但我想知道在setter(new Teste
)之后_Z
发生了什么,我对指针/引用还没有太多了解,但是对于逻辑我猜它正在被交换为新变量
如果它泄漏了,有没有办法在不必再次将 a 设置为 _Z
的情况下做到这一点?
因为地址没有改变,只是分配的直接内存
我打算使用*&
而不仅仅是指针,但它会有所不同吗?
【问题讨论】:
我认为确保不泄漏内存的最佳方法是不要尝试使用疯狂的技巧。这是什么:一个类,其目的是保存一个整数并尝试管理另一个动态分配的自身实例的内存? 删除宏有什么用? :\ 你应该使用像 auto_ptr 这样的智能指针。如果有的话,为什么在删除后将变量设置为零?你已经完成了它,你所做的只是引入了一个奇怪的宏。更喜欢堆栈分配。 答案:是的,您正在泄漏“a”。但是代码写得非常糟糕,以至于它有很多机会到处泄漏。每次 Z() 或 Z(Teste*) 都有潜在的泄漏。您没有充分指定所有权语义,并且您似乎使用普通方法作为构造机制的一部分。说白了就是糟糕的 OO 代码,甚至更糟糕的 C++ 代码。 另外,删除的时候不需要检查null,删除null是完全可以接受的,这样会导致无操作。 @sbi - 因为它除了测试和学习指针使用之外没有其他目的? 【参考方案1】:此行存在内存泄漏:
b->Z(new Teste);
因为函数的定义:
void Z(Teste *value)
value->AnyNum = 100;
*_Z = *value;
看起来像没有参数的 Z 应该是一个 getter,而有参数是一个 setter。我怀疑你打算这样做:
void Z(Teste *value)
value->AnyNum = 100;
_Z = value;
(注意第三行)也就是说,将指针“值”分配给指针“_Z”,而不是将指向的值复制到 Z 指向的值上。这样,第一次内存泄漏将得到解决,但代码仍然会有一个,因为 _Z 可能已经持有一个指针。所以你必须这样做:
void Z(Teste *value)
value->AnyNum = 100;
delete _Z; // you don't have to check for null
_Z = value;
正如另一条评论中提到的,真正的解决方案是使用智能指针。这是相同代码的更现代的方法:
using namespace std;
class Teste
private:
boost::shared_ptr<Teste> Z_;
public:
Teste() : AnyNum(5), Z_(NULL)
boost::shared_ptr<Teste> Z()
Z_.reset(new Teste);
return Z_;
void Z(boost::shared_ptr<Teste> value)
value->AnyNum = 100;
Z_ = value;
int AnyNum;
;
int main(int argc, char *argv[])
boost::shared_ptr<Teste> b = new Teste, a;
a = b->Z();
cout << "a->AnyNum: " << a->AnyNum << "\n";
b->Z(boost::shared_ptr<Teste>(new Teste));
cout << "a->AnyNum: " << a->AnyNum << "\n";
return 0;
【讨论】:
谢谢,这澄清了我的问题。 shared_ptr 的目的是不必删除析构函数中的内容? shared_ptr 和 auto_ptr 以及其他智能指针的目的是不必显式删除内容,而是将其自动删除。智能指针并不完美,但它们消除了所有棘手的问题。 shared_ptr 使用 RAII 模式 (en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization) 保护资源。一个很好的副作用是您不必编写任何显式删除,并且它澄清了所有权语义,即 Teste 是否“拥有”(负责删除)它传递的指针?原文不清楚,但在重写的代码中,很明显 Teste 不是“拥有”而是“共享”它们。【参考方案2】:是的,有:
void Z(Teste *value)
value->AnyNum = 100;
*_Z = *value; // you need assignment operator
编译器生成的赋值运算符不会进行深拷贝,而是进行浅拷贝。您需要做的是为Teste
编写一个合适的赋值运算符(可能还有一个复制构造函数)。此外,您不必在删除指针之前检查指针是否为 NULL:
~Teste()
// no need for checking. Nothing will happen if you delete a NULL pointer
if (_Z != NULL)
DELETE(_Z);
【讨论】:
复制构造函数必须手动构建吗?例如: deepcopy(a, b) a->m = b->m; 等等... 如果你的类有一个指向它自身之外的任何东西的指针,而且它不仅仅是提供给它使用的东西(比如文件写入类的文件指针),它需要确保它被复制正确并正确删除。它通常需要手动析构函数、复制构造函数和赋值运算符。 Z 函数不检查空 _z 指针。您有一个可以将 _z 保留为空的构造函数,因此(使用不同的 main)可能会崩溃。 “b->Z(新睾丸);”应该是“b->Z (Teste ());” (使用临时的,确保它破坏)。虽然 AraK 关于析构函数 NULL 检查是正确的,但您仍然可以保留它——它警告读者指针可能为空。哦,你应该同时学习智能指针和哑指针,所以尽管智能指针cmets,IMO你还是会因为学习这些东西而获得学分。【参考方案3】:您还有另一个问题:_Z 不是您应该使用的标识符。一般来说,最好避免前导下划线,特别是双下划线或下划线后跟一个大写字母是为实现保留的。
【讨论】:
参见。 ***.com/questions/228783/… 是的,这只是一个例子 我不明白这个规则...例如,如果我有一个名为_Hmm的成员,它不会受到操作系统或类似的影响,对吧?指令是 STUFF 等...如果它们不影响它,是否使用下划线,它取决于开发人员的喜好?就一个问题! xD 阅读我链接的那篇文章;它解释了关于下划线的所有规则。如果您想 100% 安全而不担心规则,请不要在标识符中使用下划线。 如果您有一个名为 func 的标识符,然后移至 C99,您的代码将失败,因为他们将该标识符添加到需要实现提供的那些标识符中。这同样适用于以 _ 开头的任何其他标识符。如果您的变量是 _THREAD 并且您移动到操作系统决定将 _THREAD 设为宏,那么您的代码将会中断。【参考方案4】:真是一团糟! 由于选择的标识符名称以开头,整个程序很难阅读:
#ifndef DELETE
#define DELETE(var) delete var, var = NULL
#endif
我觉得这很丑。 使用类时,它似乎非常不实用。您可以在变量超出范围的情况下使用它,但在析构函数中浪费时间。我认为将代码包装在一些智能指针中会更容易:
class Teste
private:
Teste *_Z;
public:
Teste()
~Teste() // Delete the _Z pointer.
Teste *Z();
void Z(Teste *value);
;
好的。您有一个在析构函数中删除的指针成员。 这意味着您正在获得指针的所有权。这意味着适用四的规则(类似于三规则但适用于所有权规则)。这意味着您基本上需要编写 4 个方法,否则编译器生成的版本会弄乱您的代码。你应该写的方法是:
A Normal (or default constructor)
A Copy constructor
An Assignment operator
A destructor.
您的代码只有其中两个。你需要写另外两个。 或者您的对象不应该拥有 RAW 指针的所有权。 IE。使用智能指针。
Teste *_Z;
这是不允许的。 保留以下划线和大写字母开头的标识符。 您冒着 OS 宏弄乱您的代码的风险。停止使用下划线作为标识符的第一个字符。
~Teste()
if (_Z != NULL)
DELETE(_Z);
这不是必需的。简单的删除 _Z 就可以了。 _Z 超出范围,因为它在析构函数中,因此无需将其设置为 NULL。 删除操作符可以很好地处理 NULL 指针。
~Test()
delete _Z;
Teste *Z()
_Z = new Teste;
return _Z;
如果您多次调用 Z() 会发生什么(PS 将 * 放在 Z 旁边而不是 Teste 旁边会使它难以阅读)。 每次调用 Z() 时,都会为成员变量 _Z 赋予一个新值。但是旧值会发生什么?基本上你正在泄漏它。也可以通过返回一个指向拥有的对象的指针 在 Teste 内部,您正在给其他人滥用该对象的机会(删除它等)。不是很好。此方法没有明确的所有权。
Teste& Z()
delete _Z; // Destroy the old value
_Z = new Teste; // Allocate a new value.
return *_Z; // Return a reference. This indicates you are retaining ownership.
// Thus any user is not allowed to delete it.
// Also you should note in the docs that it is only valid
// until the next not const call on the object
void Z(Teste *value)
value->AnyNum = 100;
*_Z = *value;
您正在将新构造的对象(包含指针)的内容复制到另一个动态创建的对象中! 如果没有先分配 _Z 会发生什么。构造函数将其设置为 NULL,因此不能保证它具有有效值。 您分配的任何对象也应该删除。但是这里的值是动态分配的,传递给 Z 但从未被释放。你逃脱的原因是因为指针是 c opied 到 _Z 并且 _Z 在其析构函数被销毁时被删除。
Teste *b = new Teste, *a;
这真的很值得读。不要偷懒把它写出来。 这被认为是不好的风格,你将永远无法通过任何代码审查。
Teste* b = new Teste;
Teste* a; // Why not set it to NULL
a = b->Z();
获取 a 的 ab 对象。但是谁在破坏对象 a 或 b?
b->Z(new Teste);
之后就变得太复杂了。
【讨论】:
感谢您的帖子,确实很有帮助。我写的这段代码不是我的项目,我只是想了解一些关于引用和 ptr 的概念。您说当您“拥有”变量时,getter 应该返回引用而不是指针,对吗?生病检查一下!这解决了我的问题。我的问题不太好......我想知道的是,如何确保 *a 指针在使用 setter 方法后仍然可以使用,明白吗?我的英语不及格有时单词就是不来...非常感谢您和大家的帮助。这个网站就是这么酷。 你好,赋值运算符就像复制构造函数?即:应该通过从A删除和复制到B来分配? 基本上是的。但是您可以只复制指针的值。您必须复制内容。【参考方案5】:(我试图将其添加为注释,但是这搞砸了代码..)
我也强烈建议不要使用
#ifndef DELETE
#define DELETE(var) delete var, var = NULL
#endif
而是类似
struct Deleter
template< class tType >
void operator() ( tType*& p )
delete p;
p = 0;
;
用法:
Deleter()( somePointerToDeleteAndSetToZero );
【讨论】:
这需要什么课程?一个简单的函数有什么问题?template<class T> void do_delete(T*& p) delete p;p=0;
一个简单的宏有什么问题?
@sbi 功能确实是一样的,我使用了结构,因为它可以传递给 std::for_each 等。 @Jonathan,对于可以用实际语言表达的任何内容,您都应该避免使用宏。这是一个简单宏问题的完美示例:parashift.com/c++-faq-lite/inline-functions.html#faq-9.5
@Jonathan:你有没有这样尝试过你的宏:DELETE(p+idx)
?
@stijn:你肯定有一个非常有效的观点。不过,我会同时提供两者,因为 Deleter()(p)
语法太尴尬了。【参考方案6】:
(不是真正的答案,但不会做评论)
您定义宏的方式很容易出现细微的错误(而到目前为止没有人发现它的事实只是证明了这一点)。考虑您的代码:
if (_Z != NULL) // yes, this check is not needed, but that's not the point I'm trying to make
DELETE(_Z);
预处理器通过后会发生什么:
if (_Z != 0)
delete _Z; _Z = 0;
如果您仍然看不到它,让我正确缩进它:
if (_Z != 0)
delete _Z;
_Z = 0;
考虑到特定的 if 条件,这没什么大不了的,但它会与其他任何事情一起爆炸,您将花费 年龄 试图弄清楚为什么您的指针突然为 NULL。这就是为什么内联函数比宏更受欢迎的原因——把它们弄乱更难。
编辑:好的,你在宏定义中使用了逗号,所以你很安全......但我仍然会说在这种情况下使用 [inline] 函数更安全。我不是永远不要使用宏的人之一,但我不会使用它们,除非它们是绝对必要的,而且在这种情况下它们不是
【讨论】:
【参考方案7】:void Z(Teste *value) 值->AnyNum = 100; *_Z = *值;
和
b->Z(new Teste);
造成内存泄漏
'new Teste' 永远不会被删除,而是您正在做的是分配一个新对象作为参数,然后使用 *_Z = *value 复制其中的任何内容,但调用后该对象不会被删除。
如果你要写作
Test* param - new Teste;
b->Z(param)
delete param;
这样会更好
当然大多数人会使用 boost::shared_ptr 或类似的东西来避免关心删除等
【讨论】:
new Teste 在类的析构函数中被删除,但旧的 _Z 永远不会被删除,对吧?促进?快速谷歌告诉我是一个非常有名的图书馆。也许我稍后会看看,ty以上是关于指针和参考问题的主要内容,如果未能解决你的问题,请参考以下文章