什么是“正确的”?在 C++ 中避免别名的方法(例如,将容器的元素添加到自身时)?

发布于 2024-11-13 09:23:18 字数 651 浏览 9 评论 0原文

std::vector<int> a;
a.push_back(1);
a.push_back(a[0]);

刚刚了解到上面的代码可能非常危险。

(如果原因不明显,你并不孤单......这对我来说也不明显。)

我的问题:

  1. 处理它的“标准”方式是什么?创建一个新变量然后立即将其分配给某些东西对我来说似乎有点奇怪。有没有更好的处理方法?

  2. 如何训练自己来留意此类别名问题?您寻找什么模式?我不知道如何认识这种情况;当我了解 C 中的 restrict 关键字时,我才了解了别名,直到现在我才明白问题到底是什么。


编辑:

我很乐意接受答案,但问题的第(2)部分似乎尚未得到解答。我想知道人们使用什么策略来定位他们编写的代码中的别名错误。

到目前为止,我提出的一种策略是避免在两个参数中传递相同的值。 (在这种情况下,一个参数是隐式的,一个参数是显式的。)

还有其他容易注意到和注意的事情吗?

std::vector<int> a;
a.push_back(1);
a.push_back(a[0]);

I just learned that the code above can be very dangerous.

(If it's not obvious why, you're not alone... it wasn't obvious to me either.)

My questions:

  1. What is the "standard" way of dealing with it? Making a new variable and then assigning it immediately to something afterward seems a bit weird to me. Is there a better way of dealing with it?

  2. How do you train yourself to watch out for aliasing issues like this? What pattern(s) do you look for? I have no idea to recognize this situation; I only learned about aliasing when I learned about the restrict keyword in C, and only now do I understand what the issue really is.


Edit:

I'd love to accept an answer, but it doesn't seem like part (2) of the question has been answered. I'm wondering what strategies people use to locate aliasing mistakes in code they have written.

One strategy I've come up with so far is to avoid passing in the same value for in two parameters. (In this case, one parameter is implicit and one explicit.)

Are there any other easy things to notice and watch out for?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

聚集的泪 2024-11-20 09:23:18

编辑:从技术上讲,如果包含的类型具有不抛出复制构造函数,标准并不强制要求这是正确的。我不知道有哪个实现不满足这一点,因为当通用实现在所有情况下都同样有效时,它需要生成两个 push_back 实现。

混叠通常是一个问题,但在这种特殊情况下却不是。代码:

assert( v.size() > 0 );
v.push_back( v[0] );

通过异常保证,标准 (C++03) 保证其正确性(这是不实现自己的容器的一个很好的理由,您可能不会得到正确的结果)。特别是 §23.1 [lib.container.requirements] / 10 条指令:

除非另有说明(参见 23.2.1.3 和 23.2.4.3)[注意:这两个引用均引用 dequevector< 上的 insert /code> 分别]本条款中定义的所有容器类型均满足以下附加要求:

—如果push_back() 或push_front() 函数抛出异常,则该函数没有效果

其中重要的一点是,如果操作中抛出任何异常,容器将保持不变,这意味着没有迭代器会失效,这反过来又意味着原始内存区域保持不变,直到保证不会抛出异常(析构函数的异常双关语)。因为通常复制构造函数可以抛出异常,所以实现必须确保在销毁任何对象之前执行所有复制。

这在 C++0x 中变得更加明显,因为对象不是从一个位置复制到另一个位置,而是移动。因为新元素的副本可能会抛出异常,所以它必须在执行任何移动之前执行,否则您将陷入原始容器中的某些对象已被删除的情况。无效。

EDIT: Technically, the standard does not mandate that this is correct if the contained type has a no-throw copy constructor. I don't know any implementation where this does not hold anyway, as it would require producing two implementations of push_back when the generic one is just as efficient in all cases.

Aliasing is a problem in general, but not in this particular case. The code:

assert( v.size() > 0 );
v.push_back( v[0] );

Is guaranteed to be correct by the standard (C++03) through the exception guarantees (which are a really good reason not to implement your own containers, you will probably not get them right). In particular §23.1 [lib.container.requirements] / 10 dictattes:

Unless otherwise specified (see 23.2.1.3 and 23.2.4.3) [NOTE: both those references refer to insert on deque and vector respectively] all container types defined in this clause meet the following additional requirements:

— if an exception is thrown by a push_back() or push_front() function, that function has no effects.

Where the important bit is that if any exception is thrown in the operation, the container is left untouched, and that means that no iterator gets invalidated, which in turns means that the original region of memory is left untouched until it is guaranteed that no exceptions will be thrown (with the exception pun intended, of destructors). Because in general copy constructors can throw, the implementation must ensure that all copies are performed before destroying any object.

This becomes more evident in C++0x, when objects are not copied from one location to another, but rather moved. Because the copy of the new element might throw, it has to be performed before any of the moves are executed, or else you would be left in a situation where some of the objects in the original container have been invalidated.

油焖大侠 2024-11-20 09:23:18

我想这会是安全的:

std::vector<int> a(1);
a.push_back(1);
a.push_back(int(a[0]));

I guess this would be safe:

std::vector<int> a(1);
a.push_back(1);
a.push_back(int(a[0]));
冷血 2024-11-20 09:23:18

push_back(const T& el); 实现中检查 el 是否位于数组或其他内部存储内部。这是处理此类问题的唯一政治正确的方式。

容器应该将其作为不同的容器来处理 - 不同的安全规则。

In push_back(const T& el); implementation to check if el is inside array or other internal storage. That is the only politically correct way of dealing with such problems.

Container should handle this as different containers - different safety rules.

北笙凉宸 2024-11-20 09:23:18

这对您来说可能不是一个有用的答案,但恕我直言,“正确”的方法是容器类应该正确处理别名,以便调用者不必担心它。特别是,push_back()(或等效函数)应该执行以下操作:

// C++-ish pseudo-code, exception-safety left as an exercise for the reader
void push_back(const T & t)
{
   if (current_size == alloced_size)
   {
      // Oops, our data array is full.  Time to trade it in for a bigger one
      T * newArray = new T[alloced_size*2];
      copy_items(newArray, current_array, current_size);
      newArray[current_size++] = t;
      delete [] current_array;    // delete old array only AFTER all references to t
      current_array = new_array;
      alloced_size *= 2;
   }
   else current_array[current_size++] = t;
}

This probably isn't a useful answer for you, but IMHO the "right" way is that the container class should handle aliasing correctly, so that the caller doesn't have to worry about it. In particular, push_back() (or equivalent) should do the following:

// C++-ish pseudo-code, exception-safety left as an exercise for the reader
void push_back(const T & t)
{
   if (current_size == alloced_size)
   {
      // Oops, our data array is full.  Time to trade it in for a bigger one
      T * newArray = new T[alloced_size*2];
      copy_items(newArray, current_array, current_size);
      newArray[current_size++] = t;
      delete [] current_array;    // delete old array only AFTER all references to t
      current_array = new_array;
      alloced_size *= 2;
   }
   else current_array[current_size++] = t;
}
趁年轻赶紧闹 2024-11-20 09:23:18

我只是即兴发挥,所以请不要认为这是福音,但这行得通吗?

a.push_back(1);
a.push_back(&(new int(a[0])));

I'm just winging this, so please don't consider it gospel, but would this work?

a.push_back(1);
a.push_back(&(new int(a[0])));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文