【问题标题】:Global const string& smells bad to me, is it truly safe?Global const string& 对我来说很难闻,它真的安全吗?
【发布时间】:2010-11-04 22:24:27
【问题描述】:

我正在查看一位同事的代码,我看到他在全局范围内定义了几个常量:

const string& SomeConstant = "This is some constant text";

就我个人而言,这对我来说很糟糕,因为引用指的是我假设是从给定 char 数组构造的“匿名”对象。

从语法上讲,它是合法的(至少在 VC++ 7 中),而且它似乎可以运行,但实际上我宁愿让他删除 &,这样它的作用就不会模棱两可了。

那么,这真的安全合法吗?我很着迷?正在构造的临时对象是否有保证的生命周期?我一直认为以这种方式使用的匿名对象在使用后会被破坏......


所以我的问题也可以推广到匿名对象的生命周期。标准是否规定了匿名对象的生命周期?它会与同一范围内的任何其他对象具有相同的生命周期吗?还是只给出表达式的生命周期?


此外,在本地执行时,其范围显然不同:

class A
{
    string _str;

public:
    A(const string& str) :
        _str(str)
    {
        cout << "Constructing A(" << _str << ")" << endl;
    }

    ~A()
    {
        cout << "Destructing A(" << _str << ")" << endl;
    }
};

void TestFun()
{
    A("Outer");
    cout << "Hi" << endl;
}

演出:

构造A(外部); 破坏A(外); 嗨

【问题讨论】:

  • 有人能回答为什么它比-不-使用参考更可取吗?
  • 不是。或者更确切地说,该标准基本上说,如果它是全局的,则它具有几乎相同的语义。 (即,临时的生命周期绑定到它所绑定的 const 引用的生命周期,或类似的东西。)
  • 但是,在这种情况下,您会多花 4 个字节,不是吗? 4作为参考和幕后临时的空间?
  • 请定义“匿名对象”的含义。该术语不在标准中。
  • 他将其作为参考的原因是什么?

标签: c++ string object constants anonymous


【解决方案1】:

这是完全合法的。在程序结束之前不会被破坏。

编辑:是的,保证:

"所有没有动态的对象 存储时长,没有线程 存储持续时间,并且不是本地的 具有静态存储持续时间。这 这些对象的存储应持续 在课程期间 (3.6.2, 3.6.3)。”

--2008 Working Draft, Standard for Programming Language C++,第 3.7.1 页。 63

正如 Martin 所说,这不是全部答案。标准草案进一步说明(§ 12.2, p. 250-1):

"创建类类型的临时对象 在各种情况下:绑定右值 参考(8.5.3)[...]即使 临时对象的创建 被避免(12.8),所有的语义 应尊重限制,如同 临时对象已创建。 [...] 临时对象被销毁 作为评估的最后一步 完整表达式 (1.9) (词法上) 包含它们所在的位置 创建的。 [...] 有两种情况 其中临时人员被销毁 与结束点不同的点 充分表达。 [...] 第二 上下文是绑定引用的时间 到一个临时的。临时的 引用被绑定或 临时的,即完整的对象 引用的子对象的 是绑定持续存在的生命周期 除非另有说明,否则参考 下面。”

我在 g++ 中进行了测试,如果这能让你感觉更好。 ;)

【讨论】:

  • 这有保证吗?我知道它在 Visual C++ 中的行为方式是这样,但我想确保根据 C++ 标准,以这种方式创建的匿名对象在程序的整个生命周期内都存在。
  • 所以我想我真正的问题是,从 C++ 标准的角度来看,在范围(全局等)中创建的匿名对象是否也获得相同的范围,或者匿名对象只为使用它们的表达式?
  • 该部分标准不适用。在分配的 RHS 上创建的临时值具有静态存储期限。它通常会在表达式完成时被销毁。它没有被销毁的唯一原因是它绑定到一个 const & 并因此延长了它的生命周期。
  • “保证”。是的,对。它与每个编译器供应商向您保证他们的编译器没有错误 - 永远 - 并且完全、100%、不可协商的标准兼容一样有保证。删除 &。这是毫无意义和不好的做法。
  • 附带说明:被引用绑定的临时对象是通过调用临时对象的原始析构函数而不是有界引用的析构函数来销毁的。换句话说:如果将 Derived 绑定到 Base&,则临时的生命周期将延长到 Base& 的生命周期,并且当引用超出范围时 ~Derived 将被调用(无论 Base 是否具有虚拟析构函数) 所以你可以在没有 vtable 的情况下获得“虚拟析构函数”语义
【解决方案2】:

是的,它是有效且合法的。

const string& SomeConstant = "This is some constant text";

// Is equivalent too:

const string& SomeConstant = std::string("This is some constant text");

因此,您正在创建一个临时对象。
这个临时对象绑定到一个 const&,因此它的生命周期也延长到它所绑定的变量的生命周期(即比创建它的表达式长)。

这是由标准保证的。

注意:

虽然是合法的。我不会使用它。最简单的解决方案是将其转换为 const std::string。

用法:

在这种情况下,因为变量在全局范围内,所以它在程序的整个长度内都有效。所以只要执行进入main()就可以使用,退出main()后不应该访问。

虽然在此之前它在技术上可能是可用的,但您在全局对象的构造函数/析构函数中的使用应该与已知的全局变量初始化顺序问题相结合。

额外的想法:

另一方面,这不会遇到问题:

char const* SomeConstant = "This is some constant text";

并且可以在任何时候使用。只是一个想法。

【讨论】:

  • 非常真实,非常真实。我通常使用 const char * const 除非我需要使用较重的字符串,或者如果我想对它使用 boost 或 std 字符串 fxns。
【解决方案3】:

这可能是合法的,但仍然很难看。省略参考!

const string SomeConstant = "This is some constant text";

【讨论】:

  • 这就是我所倾向于的,因为 & 不会给你买任何东西,真的,在这种情况下。但如果它是合法的并且是正式的,我不想为此敲他。
  • 我不会为此敲他的。如果它有效,我会留下它。我认为你的时间最好花在看他的设计上。
  • 我通常会这样做,我只是想确保它是便携的。它碰巧在 VC++7 上工作,但如果它是非标准的并且他们改变了它,它可能成为一种责任。但是,如果它符合标准,我可以接受。
  • 相信我,我不想成为代码纳粹,但他做过一些非常愚蠢的事情,比如之前从函数返回对局部变量的引用,所以我试图确保这是在我让它滑动之前合法。
  • 我认为它引起了所有这些争论的事实应该足以让你相信没有引用的代码会更好,即使它是合法的。其他人后来看到它可能会有类似的不安感觉,并引发另一波争论,仅仅是因为它并不常见。
【解决方案4】:

它既丑又合法。

【讨论】:

    【解决方案5】:

    使用const 引用扩展临时变量是合法的,Alexandrescu 的ScopeGaurd 使用它,请参阅Herb Sutter 的出色解释A candidate for the "Most important const"

    话虽如此,这个具体案例是对 C++ 的这一特性的滥用,应该删除引用,留下一个普通的 const string

    【讨论】:

      【解决方案6】:

      通过将其声明为 const (这意味着它不能更改)然后将其设为引用,这意味着有人可能会更改它,至少看起来是一种不好的形式。另外,我相信你明白,全局变量是不好的,而且很少需要。

      【讨论】:

      • 它不是一个全局变量,它是一个全局标识符 (const)。但这是挑剔的。
      【解决方案7】:

      好的,如果我离题了,请大家纠正我,但听完大家的出色回答后,我得出以下结论:

      A) 它在语法和逻辑上都是合法的,& 将临时/匿名的生命周期从表达式级别之外扩展到引用的生命周期。我在 VC++7 中验证了这一点:

      class A { 
          public: A() { cout << "constructing A" << endl; }
          public: ~A() { cout << "destructing A" << endl; }
      };
      
      void Foo()
      {
          A();
          cout << "Foo" << endl;
      }
      
      void Bar()
      {
          const A& someA = A();
          cout << "Bar" << endl;
      }
      
      int main()
      {
          Foo();    // outputs constructing A, destructing A, Foo
          Bar();    // outputs constructing A, Bar, destructing A
          return 0;
      }
      

      B)虽然它是合法的,但它可能会导致对实际生命周期的一些混淆,并且在这些情况下,引用不会给您任何将其声明为非引用的好处,因此可能应该避免引用,甚至可能是额外的空间。既然没有任何好处,那就是不必要的混淆。

      感谢所有答案,这是一个非常有趣的讨论。所以总而言之:是的,它在语法上是合法的,不,随着生命周期的延长,它在技术上并不危险,但它不会增加任何东西,并且可能会增加成本和混乱,所以为什么要打扰。

      听起来对吗?

      【讨论】:

      • 是的,您现在已经掌握了它。这是合法的,安全的,并且可能不是故意的混淆。但你是对的,不需要裁判。另外,这篇文章的代码有一些小错误,所以我编辑了它。
      • 感谢您的编辑,我正在输入代码的缩短版本以保持简短:-)
      猜你喜欢
      • 2013-07-31
      • 2011-02-05
      • 2011-12-16
      • 1970-01-01
      • 2020-06-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-17
      相关资源
      最近更新 更多