返回c++引用变量的做法是邪恶的吗?

我认为这有点主观;我不确定意见是否一致(我见过许多返回引用的代码片段)。

根据对我刚才问的这个问题,关于初始化引用的评论,返回引用可能是邪恶的,因为[据我所知]它更容易错过删除它,这可能导致内存泄漏。

这让我很担心,因为我已经遵循了一些例子(除非是我想象的事情),并且在相当多的地方这样做了……我误解了吗?它邪恶吗?如果是的话,到底有多邪恶?

我觉得因为我的指针和引用的混合包,加上我是c++的新手,以及完全不知道什么时候用什么,我的应用程序一定是内存泄漏的地狱…

另外,我知道使用智能/共享指针被普遍认为是避免内存泄漏的最佳方法。

356541 次浏览

这并不邪恶。像c++中的许多东西一样,如果使用正确,它是好的,但在使用它时应该注意许多陷阱(比如返回对局部变量的引用)。

有一些好的东西可以用它来实现(比如map[name] = "hello world")

不。

不好的是引用动态分配的对象而丢失了原来的指针。当你new一个对象时,你承担了拥有一个有保证的delete的义务。

但是看一下,例如,operator<<: 必须返回一个引用,或者

cout << "foo" << "bar" << "bletch" << endl ;

不能工作。

它不仅不邪恶,而且有时是必不可少的。例如,如果不使用引用返回值,就不可能实现std::vector的[]操作符。

一般来说,返回引用是非常正常的,并且经常发生。

如果你的意思是:

int& getInt() {
int i;
return i;  // DON'T DO THIS.
}

那是各种各样的邪恶。堆栈分配的i将消失,并且你没有引用任何东西。这也是恶的。

int& getInt() {
int* i = new int;
return *i;  // DON'T DO THIS.
}

因为现在客户最终要做一些奇怪的事情:

int& myInt = getInt(); // note the &, we cannot lose this reference!
delete &myInt;         // must delete...totally weird and  evil


int oops = getInt();
delete &oops; // undefined behavior, we're wrongly deleting a copy, not the original

请注意,右值引用仍然只是引用,所以所有邪恶的应用程序都是一样的。

如果你想要分配一些超出函数范围的东西,使用智能指针(或者一般来说,一个容器):

std::unique_ptr<int> getInt() {
return std::make_unique<int>(0);
}

现在客户端存储了一个智能指针:

std::unique_ptr<int> x = getInt();

引用也可以用于访问你知道生命期在更高级别上保持打开的东西,例如:

struct immutableint {
immutableint(int i) : i_(i) {}


const int& get() const { return i_; }
private:
int i_;
};

在这里,我们知道返回i_的引用是可以的,因为调用我们的东西管理类实例的生命周期,所以i_将至少存在这么长时间。

当然,这并没有什么错:

int getInt() {
return 0;
}

如果生命期应该留给调用者,而你只是在计算值。

总结:如果对象的生命周期不会在调用后结束,则可以返回引用。

有两种情况:

  • Const引用——有时是个好主意,特别是对于重载对象或代理类,编译器优化

  • 非const引用——有时是个坏主意,会破坏封装

两者都有同样的问题——可能指向被破坏的对象……

我建议在需要返回引用/指针的许多情况下使用智能指针。

另外,还要注意以下几点:

有一个正式的规则- c++标准(如果你感兴趣,请参阅13.3.3.1.4节)声明临时对象只能绑定到const引用-如果你试图使用非const引用,编译器必须将其标记为错误。

"返回一个引用是邪恶的, 只是(据我所知)它造就了它

不正确的。返回引用并不意味着所有权语义。也就是说,只是因为你这样做:

Value& v = thing->getTheValue();

…并不意味着你现在拥有由v引用的内存;

然而,这是可怕的代码:

int& getTheValue()
{
return *new int;
}

如果你因为"你不需要那个实例上的指针"而做这样的事情,那么:1)如果你需要引用,只需要解引用指针,2)你最终会需要指针,因为你必须用delete匹配new,你需要一个指针来调用delete。

您应该返回一个对现有对象的引用,该对象不会立即消失,并且您不打算对其进行任何所有权转移。

永远不要返回对局部变量之类的引用,因为它不会在那里被引用。

你可以返回一个独立于函数的引用,你不希望调用函数负责删除它。这是典型的operator[]函数的情况。

如果你正在创建一些东西,你应该返回一个值或一个指针(常规或智能)。您可以自由地返回一个值,因为它将进入调用函数中的变量或表达式。永远不要返回指向局部变量的指针,因为它会消失。

函数作为左值(也就是返回非const引用)应该从c++中删除。这太不直观了。Scott Meyers想要一个min()的行为。

min(a,b) = 0;  // What???

这并不是真正的进步

setmin (a, b, 0);

后者甚至更有意义。

我意识到函数作为左值对于c++风格的流很重要,但值得指出的是,c++风格的流很糟糕。我不是唯一一个这样认为的人……我记得Alexandrescu有一篇关于如何做得更好的文章,我相信boost也试图创建一个更好的类型安全的I/O方法。

我遇到了一个真正的问题,它确实是邪恶的。本质上,开发人员返回了对vector中对象的引用。太糟糕了!!

完整的细节我在一月份写过:http://developer-resource.blogspot.com/2009/01/pros-and-cons-of-returing-references.html

关于糟糕的代码:

int& getTheValue()
{
return *new int;
}

实际上,内存指针在返回后丢失。但是如果你像这样使用shared_ptr:

int& getTheValue()
{
std::shared_ptr<int> p(new int);
return *p->get();
}

内存在返回后不会丢失,并将在分配后释放。

返回引用通常用于c++中大型对象的操作符重载,因为返回值需要复制操作。(在操作符重载中,我们通常不使用指针作为返回值)

但是返回引用可能会导致内存分配问题。由于对结果的引用将作为对返回值的引用传递给函数,因此返回值不能是自动变量。

如果你想使用返回引用,你可以使用静态对象缓冲区。 例如< / p >

const max_tmp=5;
Obj& get_tmp()
{
static int buf=0;
static Obj Buf[max_tmp];
if(buf==max_tmp) buf=0;
return Buf[buf++];
}
Obj& operator+(const Obj& o1, const Obj& o1)
{
Obj& res=get_tmp();
// +operation
return res;
}

通过这种方式,您可以安全地使用返回引用。

但你总是可以使用指针而不是引用在函数中返回值。

我认为使用reference作为函数的返回值比使用pointer作为函数的返回值更直接。 其次,使用返回值所指向的静态变量总是安全的。< / p >

最好的方法是创建对象并将其作为引用/指针形参传递给分配该变量的函数。

在函数中分配对象并将其作为引用或指针返回(但指针更安全)是一个坏主意,因为会在函数块的末尾释放内存。

我发现这些答案不令人满意,所以我要发表我的意见。

让我们来分析以下案例:

错误的用法

int& getInt()
{
int x = 4;
return x;
}

这显然是错误的

int& x = getInt(); // will refer to garbage

静态变量的使用

int& getInt()
{
static int x = 4;
return x;
}

这是正确的,因为静态变量在程序的整个生命周期中都存在。

int& x = getInt(); // valid reference, x = 4

这在实现单例模式时也很常见

class Singleton
{
public:
static Singleton& instance()
{
static Singleton instance;
return instance;
};


void printHello()
{
printf("Hello");
};


};

用法:

 Singleton& my_sing = Singleton::instance(); // Valid Singleton instance
my_sing.printHello();  // "Hello"
    

运营商

标准库容器在很大程度上依赖于返回引用的操作符的使用

T & operator*();

可用于以下

std::vector<int> x = {1, 2, 3}; // create vector with 3 elements
std::vector<int>::iterator iter = x.begin(); // iterator points to first element (1)
*iter = 2; // modify first element, x = {2, 2, 3} now

快速访问内部数据

有些时候&可用于快速访问内部数据

Class Container
{
private:
std::vector<int> m_data;


public:
std::vector<int>& data()
{
return m_data;
}
}

的用法:

Container cont;
cont.data().push_back(1); // appends element to std::vector<int>
cont.data()[0] // 1

然而,这可能会导致如下陷阱:

Container* cont = new Container;
std::vector<int>& cont_data = cont->data();
cont_data.push_back(1);
delete cont; // This is bad, because we still have a dangling reference to its internal data!
cont_data[0]; // dangling reference!
    Class Set {
int *ptr;
int size;


public:
Set(){
size =0;
}


Set(int size) {
this->size = size;
ptr = new int [size];
}


int& getPtr(int i) {
return ptr[i];  // bad practice
}
};

getPtr函数可以在删除甚至空对象后访问动态内存。这可能导致坏访问异常。相反,应该实现getter和setter,并在返回之前验证大小。

对已接受答案的补充:

struct immutableint {
immutableint(int i) : i_(i) {}


const int& get() const { return i_; }
private:
int i_;
};

我认为这个例子是不是好吧,如果可能的话应该避免。为什么?它很容易以晃来晃去的参考结束。

用一个例子来说明这一点:

struct Foo
{
Foo(int i = 42) : boo_(i) {}
immutableint boo()
{
return boo_;
}
private:
immutableint boo_;
};

进入危险区域:

Foo foo;
const int& dangling = foo.boo().get(); // dangling reference!