Item 38. Object Identity

本文探讨了C++中自我赋值检查的有效性和必要性,并提供了优化建议。通过对比不同情况下的效率成本,指导开发者如何在实际应用中正确使用自我赋值检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

"Who am I, really?" This problem addresses how to decide whether two pointers really refer to the same object.

The "this != &other" test (illustrated below) is a common coding practice intended to prevent self-assignment. Is the condition necessary and/or sufficient to accomplish this? Why or why not? If not, how would you fix it?

T& T::operator=( const T& other ) 
{
  if( this != &other )    // the test in question
  {
    // ...
  }
  return *this;
}

Remember to distinguish between "protecting against Murphy versus protecting against Machiavelli"梩hat is, protecting against things going wrong on their own versus a malicious programmer going to great lengths trying to break your code.

Solution

graphics/bulb_icon.gif

Short answer: In most cases, it's bad only if, without the test, self-assignment is not handled correctly. Although this check won't protect against all possible abuses, in practice it's fine as long as it's being done for optimization only. In the past, some have suggested that multiple inheritance affects the correctness of this problem. This is not true, and it's a red herring.

Exception Safety (Murphy)

If T::operator=() is written using the create-a-temporary-and-swap idiom (see page 47), it will be both strongly exception-safe and not have to test for self-assignment. Period. Because we should normally prefer to write copy assignment this way, we shouldn't need to perform the self-assignment test, right?

Guideline

graphics/guideline_icon.gif

Never write a copy assignment operator that relies on a check for self-assignment in order to work properly; a copy assignment operator that uses the create-a-temporary-and-swap idiom is automaticially both strongly exception-safe and safe for self-assignment,


Yes and no. It turns out that there are two potential efficiency costs when not checking for self-assignment.

  • If you can test for self-assignment, then you can completely optimize away the assignment

  • Often making code exception-safe also makes it less efficient (a.k.a. the "paranoia has a price principle").

In practice, if self-assignment happens often (rare, in most programs) and the speed improvement gained by suppressing unnecessary work during self-assignment is significant to your application (even rarer, in most programs), check for self-assignment.

Guideline

graphics/guideline_icon.gif

It's all right to use a self-assignment check as an optimization to avoid needless work.


Operator Overloading (Machiavelli?)

Because classes may provide their own operator&(), the test in question may do something completely different than is intended. This comes under the heading of "protecting against Machiavelli," though; presumably, the writer of T::operator=() knows whether his class also overloads T::operator&(). Of course, a base class could also provide operator&() in a way that breaks this test.

Note that while a class may also provide a T::operator!=(), this is irrelevant because it can't interfere with the "this != &other" test. The reason is that you can't write an operator!=() that takes two T* parameters, because at least one parameter of an overloaded operator must be of class type.

Postscript #1:

As a bonus, here's a little code joke. Believe it or not, it's been tried by well-meaning (although clearly misguided) coders.

T::T( const T& other ) 
{
  if( this != &other )
  {
    // ...
  }
}

Did you get the point on first reading?[1]

[1] Checking for self-assignment in a constructor doesn't make sense, because the other object can't be the same as our own object梠ur own object is still in the process of being created! When I first wrote that code joke, I thought (correctly) that the test should be meaningless. Still, it turns out that this one actually falls into the category of "protecting against Machiavelli." Friend and astute reader Jim Hyslop pointed out that there is a piece of (technically illegal) code using placement new that, if it were legal, would make the test meaningful:

 

T t; 
new (&t) T(t);  // illegal, but it would make the test meaningful

 

Yikes! I'm not sure whether to be happy that people appreciated my code joke, or to be deeply worried that they immediately started trying to find subversive ways to make it meaningful.

Similarly, the following code is also not valid C++, in that it's syntactically legal (a conforming compiler must accept it) but has undefined behavior (a conforming compiler may legitimately emit code that will reformat your hard drive). If the code were valid, it would also make the test meaningful:

 

T t = t; // invalid, but it would make the test meaningful 

 

Postscript #2

Note that there are other cases in which pointer comparison is not what most people would consider intuitive. Here are a couple of examples.

  • Comparing pointers into string literals is undefined. The reason is that the standard explicitly allows compilers to store string literals in overlapping areas of memory as a space optimization. For this reason, it's entirely possible to take a pointer into two different string literals and have them compare equal.

  • In general, you cannot compare arbitrary bald pointers using the builtin operators <, <=, >, and >= with well-defined results, although the results are defined in specific situations (for example, pointers to objects in the same array can be so compared). The standard library works around this limitation by saying that the library's comparison function templates, less<> and its brothers, must give an ordering of pointers. This is necessary so that you can create, say, a map with keys of pointer type梖or example, a map<T*,U> that, with the default template parameter, is a map<T*,U,less<T*> >.

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值