题 取消引用指针以获取引用是错误的吗?


我更喜欢在任何地方都使用引用,但是当你使用STL容器时,你必须使用指针,除非你  想要按值传递复杂类型。我觉得很难转换回参考,这似乎是错误的。

是吗?

澄清...

MyType *pObj = ...
MyType &obj = *pObj;

这不是'脏',因为你可以(即使只是理论上,因为你先检查它)取消引用一个NULL指针?

编辑:哦,你不知道对象是否是动态创建的。


28
2017-08-09 21:48


起源


我会说你可以随心所欲地指出一个指针,但我不确定这样的行为会暗示什么。 - Crazy Eddie
@约翰: youtube.com/watch?v=sf-5RaFnh2U#t=2m14s :) - Merlyn Morgan-Graham
@John:你能告诉我们你存储指针的对象是否是动态分配的?我认为情况确实如此,但并非绝对需要。 - Steven Sudit
@John:做 不 永远让自己处于需要删除的位置 什么,它需要被包裹起来。用一个 shared_ptr 实现,指针容器,或在C ++ 0x中 unique_ptr。另外,不要猜测优化。按值存储它们,如果性能变为a 测量 问题,存储智能指针代替值。 - GManNickG
@Steve:我可以从他的论证中推断出唯一的反对意见是性能是一个问题(因此“真的希望通过价值传递复杂的类型”并找到一种“更好的方式”来复制东西。编写代码到 劈 错误的代码不是很好的代码,这是一个黑客。应该用破坏的复制语义来修复类。 - GManNickG


答案:


在尝试将指针转换为引用之前确保指针不是NULL,并且只要引用(或保持分配,引用堆),对象将保留在作用域中,并且您将没关系,道德干净:)


16
2017-08-09 21:53



动态分配的对象如何在范围内? - Billy ONeal
空引用(不方便)未定义。 - Steven Sudit
@Billy:我们不知道它们是动态分配的,只是我们指的是它们。 - Steven Sudit
@Billy:重新阅读他们所写的内容。他们使用的是STL容器,但是他们不想按容器中的值存储容器,因此需要在插入时使用复制构造函数。他们想要一个(智能)指针的容器,指向可能动态分配的值,或者可能不是(例如静态数组)。 - Steven Sudit
智能指针可以具有空的析构函数。如果你有一个指向对象的智能指针向量,但由于某种原因想要存储堆栈分配的对象,这是一件好事。 - gnud


使用解除引用的指针初始化引用绝对没问题,无论如何都没有错。如果 p 是一个指针,如果解除引用它是有效的(例如,它不是null),那么 *p 是它指向的对象。您可以绑定对该对象的引用,就像绑定对任何对象的引用一样。显然,您必须确保引用不会超过对象(就像任何引用一样)。

例如,假设我传递了一个指向对象数组的指针。它也可以是迭代器对,或对象的向量,或者 map 对象,但我会使用数组来简化。每个对象都有一个功能, order,返回一个整数。我打电话给 bar 每个对象的功能一次,按增加的顺序 order 值:

void bar(Foo &f) {
    // does something
}

bool by_order(Foo *lhs, Foo *rhs) {
    return lhs->order() < rhs->order();
}

void call_bar_in_order(Foo *array, int count) {
    std::vector<Foo*> vec(count);  // vector of pointers
    for (int i = 0; i < count; ++i) vec[i] = &(array[i]);
    std::sort(vec.begin(), vec.end(), by_order);
    for (int i = 0; i < count; ++i) bar(*vec[i]); 
}

我的示例初始化的引用是函数参数而不是直接变量,但我可以有效地完成:

for (int i = 0; i < count; ++i) {
    Foo &f = *vec[i];
    bar(f);
}

显然是一个 vector<Foo> 是不正确的,从那以后我会打电话 bar 在...上 复制 每个对象按顺序排列,而不是按顺序排列在每个对象上。 bar 采用非常量引用,所以除了性能或其他任何东西之外,如果是,那显然是错误的 bar 修改输入。

智能指针或增强指针向量的向量也是错误的,因为我不拥有数组中的对象,当然也不能释放它们。也可能不允许对原始数组进行排序,或者如果它是a,则不可能 map 而不是一个数组。


13
2017-08-09 22:41



是的,有时你只需要一个原始指针,允许所有权在别处处理。 - Steven Sudit
如果向量的范围安全地在一些函数中,其中referands都是有效的,那么没有人会受到伤害...... - Steve Jessop


不,你还能实施吗? operator=?你必须取消引用 this 为了返回对自己的引用。

请注意,我仍然按值存储STL容器中的项目 - 除非您的对象很大,堆分配的开销意味着您使用的存储空间更多,效率也低于您的存储空间。只是按值存储项目。


4
2017-08-09 21:49



@Billy:你的开销可能是正确的。话虽如此,偶尔会有无法复制的对象。 - Steven Sudit
开销是否取决于您使用的容器类型?例如,a std::vector 以块为单位保留内存,不为添加到其中的每个元素单独分配。一个 std::set 要么 std::map 实现可以很好地为每个元素执行单独的分配,从而产生你所说的开销。或许还有其他我不考虑的事情。请详细说明。 - A. Levy
@一个。利维:A vector 将根据需要重新分配,将实例从旧缓冲区复制到新缓冲区。但是,是的,它会分配一个连续的范围并使用位置 new在这些位置实例化副本。对于每个节点,映射可能需要一个块,但是再次,它不可能复制节点。 - Steven Sudit
@Steven:除非你复制地图本身。 - Billy ONeal
这也意味着你的班级必须有一个空的/默认的ctor。这可能意味着编写额外的代码只是为了让你将它们放在容器中,当这个状态的对象无效时。看起来很乱。 - Mr. Boy


我的回答并未直接解决您最初的担忧,但似乎您遇到此问题,因为您有一个存储指针类型的STL容器。

Boost提供了 ptr_container库来解决这些类型的情况。例如,a ptr_vector 内部存储指向类型的指针,但通过其接口返回引用。请注意,这意味着容器拥有指向实例的指针并将管理其删除。

这是一个展示这个概念的简单例子。

#include <string>
#include <boost/ptr_container/ptr_vector.hpp>

void foo()
{
    boost::ptr_vector<std::string> strings;

    strings.push_back(new std::string("hello world!"));
    strings.push_back(new std::string());

    const std::string& helloWorld(strings[0]);
    std::string& empty(strings[1]);
}

2
2017-08-09 23:18



尼斯。智能指针是有效内置的,但容器提供了引用语义。 - Steven Sudit


我更喜欢在任何地方使用引用,但是当你使用STL容器时,你必须使用指针,除非你真的想按值传递复杂类型。

需要明确的是:STL容器旨在支持某些语义(“值语义”),例如“容器中的项目可以被复制”。由于引用不可重新绑定,因此它们不支持值语义(即尝试创建一个 std::vector<int&> 要么 std::list<double&>)。你是不对的,你不能把引用放在STL容器中。

通常,如果您使用的是引用而不是普通对象,那么您要么使用基类并且要避免切片,要么就是要避免复制。并且,是的,这意味着如果您想将项目存储在STL容器中,那么您将需要使用指针来避免切片和/或复制。

并且,是的,以下是合法的(虽然在这种情况下,不是很有用):

#include <iostream>
#include <vector>

// note signature, inside this function, i is an int&
// normally I would pass a const reference, but you can't add
// a "const* int" to a "std::vector<int*>"
void add_to_vector(std::vector<int*>& v, int& i)
{
    v.push_back(&i);
}

int main()
{
    int x = 5;
    std::vector<int*> pointers_to_ints;

    // x is passed by reference
    // NOTE:  this line could have simply been "pointers_to_ints.push_back(&x)"
    // I simply wanted to demonstrate (in the body of add_to_vector) that
    // taking the address of a reference returns the address of the object the
    // reference refers to.
    add_to_vector(pointers_to_ints, x);

    // get the pointer to x out of the container
    int* pointer_to_x = pointers_to_ints[0];

    // dereference the pointer and initialize a reference with it
    int& ref_to_x = *pointer_to_x;

    // use the reference to change the original value (in this case, to change x)
    ref_to_x = 42;

    // show that x changed
    std::cout << x << '\n';
}

哦,你不知道对象是否是动态创建的。

那不重要。在上面的例子中, x 在堆栈上,我们存储指针 x 在里面 pointers_to_vectors。当然, pointers_to_vectors 内部使用动态分配的数组(和 delete[]当那个阵列的时候 vector 超出范围),但该数组包含指针,而不是指向的东西。什么时候 pointers_to_ints 超出范围,内部 int*[] 是 delete[]-ed,但是 int*不是 deleted。

事实上,这使得使用STL容器的指针很难,因为STL容器不会管理指向对象的生命周期。您可能想要查看Boost的指针容器库。否则,你要么(1)想要使用智能指针的STL容器(比如 boost:shared_ptr 这对STL容器是合法的)或(2)以其他方式管理指向对象的生命周期。你可能已经在做(2)。


2
2017-08-10 00:35





如果你想要实际的容器 包含 动态分配的对象,不应该使用原始指针。使用 unique_ptr 或任何类似的类型是合适的。


1
2017-08-09 21:54



unique_ptr 仅在C ++ 0x中可用,这可能是禁止的。 - Billy ONeal
@Billy:“或类似的任何类型”。我们有 auto_ptr 现在,但Boost提供了一些更好的选择。 - Steven Sudit
@Steven: auto_ptr 不能存储在STL容器内。 - Billy ONeal
@约翰: auto_ptr 没有正确的复制语义放在容器中。。 - James McNellis
@Bill:请把它作为“发明”。如果使用Boost,旧C ++中有许多非常好的智能指针。这是一个很好的总结: codesynthesis.com/~boris/blog/2010/05/24/... - Steven Sudit


它没有任何问题,但请注意,在机器代码级别上,引用是 平时 与指针相同。所以, 平时 分配给引用时,指针实际上没有被解除引用(没有内存访问)。 所以在现实生活中,引用可以是0,并且在使用引用时发生崩溃 - 什么可能比它的assignemt晚发生。

当然,严重依赖于编译器版本和硬件平台以及编译器选项和引用的确切用法。

正式解除引用0指针的行为是 未定义 因此任何事情都可能发生。这个 什么 包括它可能会立即崩溃,但也可能会在以后或永远不会崩溃。

因此,请始终确保您永远不会将0指针分配给参考 - 这样的错误很难找到。

编辑:制作“通常”斜体并添加关于官方“未定义”行为的段落。


1
2017-08-09 23:26



嗯,是C ++标准 要求 引用被实现为直接指针? - Steven Sudit
不,但大多数编译器通常会这样做。因此,在实践中经常可以对0指针进行反转并将其分配给参考,并且可能导致其他位置发生奇怪的崩溃。我更新了我的答案,更清楚地说明了这一点。 - IanH