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

时间:2010-08-09 21:48:32

标签: c++ pointers stl reference

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

是吗?

澄清......

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

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

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

7 个答案:

答案 0 :(得分:16)

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

答案 1 :(得分:13)

使用解除引用的指针初始化引用绝对没问题,无论如何都没有错。如果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,而不是按顺序在每个对象上调用barbar采用非const引用,因此除了性能或其他任何内容之外,如果map修改输入,那么显然是错误的。

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

答案 2 :(得分:4)

没有。你怎么能实现operator=?您必须取消引用this才能返回对自己的引用。

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

答案 3 :(得分:2)

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

Boost提供ptr_container库来解决这些类型的情况。例如,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]);
}

答案 4 :(得分:2)

  

我更喜欢在任何地方使用引用,但是当你使用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*不是delete d。

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

答案 5 :(得分:1)

如果您希望容器实际包含动态分配的对象,则不应使用原始指针。使用unique_ptr或任何类似的类型。

答案 6 :(得分:1)

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

当然,发生的事情在很大程度上取决于编译器版本和硬件平台以及编译器选项以及参考的确切用法。

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

所以总是要确保你永远不会给参考指定一个0指针 - 这样的bug很难找到。

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