Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/opencv/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 悬空引用(不信任原始代码/引用的生存期声明)_C++ - Fatal编程技术网

C++ 悬空引用(不信任原始代码/引用的生存期声明)

C++ 悬空引用(不信任原始代码/引用的生存期声明),c++,C++,如果GetMyClass可以(合理地)更改以返回引用,则必须确保该对象的生存期足够,例如 SomeClass* p = /* ... */; void some_function(const MyClass& a) { /* much code with many side-effects */ delete p; a.do_something(); // oops! } const MyClass& r = p->get_reference(

如果
GetMyClass
可以(合理地)更改以返回引用,则必须确保该对象的生存期足够,例如

SomeClass* p = /* ... */;

void some_function(const MyClass& a)
{
    /* much code with many side-effects */
    delete p;
    a.do_something();  // oops!
}

const MyClass& r = p->get_reference();
some_function(r);

所有权

一个直接命名对象的变量,如
const MyClass明确说明我拥有此对象。考虑<代码>易变的成员:如果您稍后更改它们,则如果读者已被声明为引用,对于读者而言,这是不确定的(对于所有的更改)。

此外,对于一个引用,它引用的对象是否没有改变并不明显。
const
引用仅表示您不会更改对象,而不是没有人会更改对象(*)。程序员必须知道,通过查找该变量的定义,该引用是引用该对象的唯一方式


(*)免责声明:尽量避免不明显的副作用

关于实际调用的析构函数有一个重要的含义。检查Gotw88、Q3和A3。我把所有东西都放在一个小测试程序中(Visual C++,所以请原谅stdafx.h)

这里有一个关于设置的小说明。B是从A派生的,但两者都没有虚拟析构函数(我知道这是一个WTF,但在这里它很重要)。CreateB()按值返回B。Main现在调用CreateB,并首先将此调用的结果存储在类型a的const引用中。然后调用CreateB,并将结果存储在类型a的值中

结果很有趣。首先-如果按引用存储,则调用正确的析构函数(B),如果按值存储,则调用错误的析构函数。第二,如果存储在引用中,析构函数只调用一次,这意味着只有一个对象。By值导致2个调用(对不同的析构函数),这意味着有2个对象

我的建议-使用const参考。至少在VisualC++中,它导致拷贝更少。如果您对编译器不确定,请使用并调整此测试程序来检查编译器。如何适应?添加复制/移动构造函数和复制赋值运算符


我很快为A类和B类添加了复制和赋值运算符

A(const A& rhs)
{
    std::cout<<"A copy constructed"<<std::endl;
}

A& operator=(const A& rhs)
{
    std::cout<<"A copy assigned"<<std::endl;
}
这证实了上面的结果(请注意,从B构造为B的A构造结果是从A派生的,因此每当调用Bs构造函数时,都会调用as构造函数)


附加测试:VisualC++将非同一引用(同样在这个例子中)作为const引用接受。此外,如果您使用auto作为类型,则会调用正确的析构函数(当然)并进行返回值优化,最终结果与const引用相同(当然,auto具有类型B而不是A)。

我不知道您为什么要这样做?除此之外,返回的值可能不是引用,因此仍有一个副本正在生成[即使该副本是由编译器生成的用于保存返回对象的未命名临时对象]。。。如果没有看到
GetMyClass()
的返回类型,我无法确定。如果函数有可能按值返回,则不希望使用引用,因为它具有误导性。如果函数保证返回一个引用,那么选择是使用返回值所引用的对象(包括考虑它的生存期可能比本地引用短),还是使用自己的对象,具有定义的生存期。@JamesKanze在我的示例中,它确实按值返回。@dan在这种情况下,没有理由使用引用。@dan和我已经阅读了您提到的文章,但它似乎没有说任何我们都不知道的东西。它没有说明为什么有人会想做那样的事情;如果是这样的话,它就不得不说没有理由,这只是糟糕的编程实践。或者一些读者可能会认为GetMyClass()通过引用而不是通过值(例如,对于单例)返回现有变量,并且此局部对象上的更改可能会影响其他对象,这些对象将在代码中的其他地方创建(稍后或在其他线程中)。即使引用是常量,在内部状态或并发运行时问题中也可能存在一些可变值。@user1837009根据该参数,我们应该到处按值传递,而不是按常量引用传递。我认为没有人会这么认为!@user1837009可能更切题:当使用由f返回的引用时函数,您(和编译器)必须考虑到对象可能会在其他地方的代码中更改其值。@JamesKanze但由于函数是按值返回的,这是代码中对该对象的唯一引用。因此其他任何东西都不能修改它。@dan但编译器必须对此进行推断。(我指的是Mats的文章,他在文章中说,大多数编译器都将引用作为隐藏的指针来实现。)如果对象返回一个对象而不是一个引用,为什么需要创建一个副本?该对象最终会出现在哪里?我说的副本是到接收函数中的临时存储区-它是一个“未命名对象”或命名对象[从技术上讲,编译器有时会制作两个副本,但返回值优化允许编译器将其制作成一个副本]。该副本很可能根据[class.copy]删除/31.但是,它需要是可复制的。如果省略了副本,那么它将在函数本身中完成。在
OtherFunction
中必须有
MyClass
对象的存储-可能是
GetMyClass
函数直接写入编译器作为优化传递的存储中。对象还会在哪里存储?Herb Sutter简单地说,复制的对象将一直保留,直到
const
引用超出范围。但是字符串对象存在
 MyClass __noname__ = GetMyClass();
 const MyClass &myclass = __noname__;
MyClass const& var = GetMyClass();
MyClass const var = GetMyClass();
MyClass version_a();
MyClass const& version_b();

const MyClass var1 =version_a();
const MyClass var2 =version_b();

const MyClass var3&=version_a();
const MyClass var4&=version_b();

const auto    var5 =version_a();
const auto    var6 =version_b();
const MyClass& rMyClass = GetMyClass();
const MyClass  oMyClass = GetMyClass();
SomeClass* p = /* ... */;

void some_function(const MyClass& a)
{
    /* much code with many side-effects */
    delete p;
    a.do_something();  // oops!
}

const MyClass& r = p->get_reference();
some_function(r);
// Gotw88.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

class A
{
protected:
    bool m_destroyed;
public:
    A() : m_destroyed(false) {}
    ~A() 
    { 
        if (!m_destroyed)
        {
            std::cout<<"A destroyed"<<std::endl;
            m_destroyed=true;
        }
    }
};

class B : public A
{
public:
    ~B() 
    { 
        if (!m_destroyed)
        {
            std::cout<<"B destroyed"<<std::endl;
            m_destroyed=true;
        }
    }
};

B CreateB()
{
    return B();
}


int _tmain(int argc, _TCHAR* argv[])
{
    std::cout<<"Reference"<<std::endl;
    {
        const A& tmpRef = CreateB();
    }
    std::cout<<"Value"<<std::endl;
    {
        A tmpVal = CreateB();
    }


    return 0;
}
Reference
B destroyed
Value
B destroyed
A destroyed
A(const A& rhs)
{
    std::cout<<"A copy constructed"<<std::endl;
}

A& operator=(const A& rhs)
{
    std::cout<<"A copy assigned"<<std::endl;
}
Reference
A constructed
B constructed
B destroyed
Value
A constructed
B constructed
A copy constructed
B destroyed
A destroyed