代码之家  ›  专栏  ›  技术社区  ›  Rob

返回对象的常量引用而不是副本

  •  70
  • Rob  · 技术社区  · 16 年前

    在重构一些代码时,我遇到了一些返回std::string的getter方法。例如:

    class foo
    {
    private:
        std::string name_;
    public:
        std::string name()
        {
            return name_;
        }
    };
    

    当然,盖特最好还是返回 const std::string& ?当前方法返回的副本效率不高。返回常量引用是否会导致任何问题?

    12 回复  |  直到 7 年前
        1
  •  55
  •   Guillaume Jacquenot mbernasocchi    7 年前

    唯一可能导致问题的方法是调用方存储引用,而不是复制字符串,并在对象被破坏后尝试使用它。这样地:

    foo *pFoo = new foo;
    const std::string &myName = pFoo->getName();
    delete pFoo;
    cout << myName;  // error! dangling reference
    

    但是,由于您的现有函数返回一个副本,所以您不会破坏任何现有代码。

        2
  •  28
  •   Ogre Psalm33    13 年前

    实际上,另一个问题 明确地 返回字符串 参考一下,事实是 std::string 通过指向内部的指针提供访问 const char* 通过 c_str() 方法。这让我调试了好几个小时。例如,假设我想从Foo获得名称,并将其传递给JNI,用于构建JStin稍后传入Java,以及 name() 正在返回副本而不是引用。我可以这样写:

    foo myFoo = getFoo(); // Get the foo from somewhere.
    const char* fooCName = foo.name().c_str(); // Woops!  foo.name() creates a temporary that's destructed as soon as this line executes!
    jniEnv->NewStringUTF(fooCName);  // No good, fooCName was released when the temporary was deleted.
    

    如果调用者要做这种事情,最好使用某种类型的智能指针或常量引用,或者至少在foo.name()方法上有一个讨厌的警告注释头。我提到JNI,因为以前的Java编码器可能特别容易受到这种类型的链接的伤害,而这种链接可能看起来无害。

        3
  •  17
  •   paercebal    16 年前

    常量引用返回的一个问题是,如果用户编写了如下代码:

    const std::string & str = myObject.getSomeString() ;
    

    用一个 std::string 返回,临时对象将保持活动并附加到str,直到str超出范围。

    但发生了什么 const std::string & ?我的猜测是,当父对象释放对象时,我们将有一个对该对象的常量引用,该对象可能会死亡:

    MyObject * myObject = new MyObject("My String") ;
    const std::string & str = myObject->getSomeString() ;
    delete myObject ;
    // Use str... which references a destroyed object.
    

    所以我倾向于使用const-reference返回(因为,无论如何,我更愿意发送引用,而不是希望编译器优化额外的临时引用),只要遵守下面的约定:“如果您希望它超出我的对象的存在范围,他们会在我的对象被破坏之前复制它。”

        4
  •  10
  •   rlerallut    16 年前

    std::string的一些实现与copy-on-write语义共享内存,因此按值返回的效率几乎与按引用返回的效率相同。 你不必担心终身问题(运行时为你做的)。

    如果你担心表现,那么 基准IT (<=压力不够大)!!!!尝试两种方法并测量收益(或不足)。如果一个更好,你真的在乎,那就用它。如果没有,那么更喜欢按价值来保护它,因为它提供了其他人提到的终身问题。

    你知道他们对假设的看法…

        5
  •  7
  •   Frank    15 年前

    好的,所以 差异 在返回副本和返回引用之间有:

    • 性能 :返回引用可能会更快,也可能不会更快;这取决于 std::string 由编译器实现实现(如其他人指出的那样)。但即使返回引用,函数调用后的赋值通常也会涉及一个副本,如 std::string name = obj.name();

    • 安全性 :返回引用可能会或可能不会导致问题(悬空引用)。如果函数的用户不知道他们在做什么,将引用存储为引用,并在提供的对象超出范围后使用它,那么就会出现问题。

    如果你想要它 快速安全 使用 Boost::共享资源 . 对象可以在内部将字符串存储为 shared_ptr 并返回 SelddPPTR . 这样,就不会复制正在运行的对象,而且始终是安全的(除非您的用户使用 get() 然后在你的对象超出范围后处理它)。

        6
  •  4
  •   Kristopher Johnson    16 年前

    我会将其更改为返回const std::string&。如果您不更改所有调用代码,调用方可能会复制结果,但不会带来任何问题。

    如果有多个线程调用name(),则会出现一个潜在的褶皱。如果返回一个引用,但稍后更改基础值,则调用方的值将更改。但是现有的代码看起来并不是线程安全的。

    看看Dima对一个潜在但不太可能出现的问题的答案。

        7
  •  3
  •   Airsource Ltd    16 年前

    可以想象,如果调用者真的想要一个副本,你可以破坏一些东西,因为他们将要改变原始版本,并且想要保留一个副本。然而,更可能的是,它实际上应该返回一个常量引用。

    最简单的方法是尝试它,然后测试它,看看它是否仍然有效,前提是您可以运行某种类型的测试。如果没有,在继续重构之前,我将集中精力首先编写测试。

        8
  •  2
  •   christopher_f    16 年前

    重要吗?只要使用现代的优化编译器,按值返回的函数就不会涉及到副本,除非它们在语义上是必需的。

    the C++ lite FAQ 对此。

        9
  •  1
  •   17 of 26    16 年前

    如果更改为常量引用,该函数的典型用法将不会中断,这种可能性非常大。

    如果调用该函数的所有代码都在您的控制之下,只需进行更改,看看编译器是否会抱怨。

        10
  •  0
  •   Joel Coehoorn    16 年前

    取决于你需要做什么。也许您希望所有调用方在不更改类的情况下更改返回值。如果返回的常量引用不会飞。

    当然,下一个论点是调用者可以自己制作副本。但是,如果您知道如何使用函数,并且知道无论如何都会发生这种情况,那么这样做可能会在代码中为您节省一步。

        11
  •  0
  •   Brett Hall    16 年前

    我通常返回const&除非不能返回。qbziz给出了一个示例,说明情况是什么。当然,qbziz还声称std::string具有copy-on-write语义,这在今天很少是真的,因为cow在多线程环境中涉及大量开销。通过返回const,您可以让调用者有义务在字符串结束时做正确的事情。但是,由于您处理的代码已经在使用中,所以您可能不应该更改它,除非分析显示复制此字符串会导致大量的性能问题。然后,如果你决定改变它,你需要仔细考虑,以确保你没有破坏任何东西。希望与您一起工作的其他开发人员不要做像dima答案中那样的粗略的事情。

        12
  •  0
  •   Community George Stocker    7 年前

    返回对成员的引用将公开类的实现。 这可以防止改变班级。在需要优化的情况下,可能对私有或受保护的方法有用。 What should a C++ getter return

    推荐文章