【问题标题】:Is there a difference between `string s("hello");` and `string s = "hello";``string s("hello");` 和 `string s = "hello";` 之间有区别吗
【发布时间】:2017-01-25 08:03:58
【问题描述】:

标题说明了一切。但是,请将string 作为任何类的占位符。

std::string s1("hello");  // construct from arguments
std::string s2 = "hello"; // ???
std::string s3;           // construct with default values
s3 = "hello";             // assign

我想知道s2 的语句是否与s1s3 的语句相同。

【问题讨论】:

标签: c++ initialization variable-assignment copy-constructor assignment-operator


【解决方案1】:

s2 的大小写是copy initialization。这是初始化,而不是 s3 的赋值。

请注意,对于std::strings1s2 的效果相同,将调用相应的构造函数(即std::string::string(const char*))来构造对象。但是拷贝初始化和direct initialization有区别(s1的情况);对于复制初始化,将不考虑显式构造函数。假设std::string::string(const char*)被声明为explicit,这意味着从const char*std::string的隐式转换是不允许的;那么第二种情况将不会再次编译。

复制初始化比直接初始化更宽松:显式构造函数不转换构造函数,也不考虑复制初始化。

【讨论】:

    【解决方案2】:

    在这种情况下,s1s2完全做同样的事情:它们都调用 const char* 的构造函数。 (为了清楚起见,有些人更喜欢使用=)。

    对于s3,调用默认构造函数,后跟operator=const char*

    【讨论】:

      【解决方案3】:

      虽然所有 3 种方法的最终结果是相同的(字符串将分配给变量),但存在一些比语法更深层次的基本差异。我将介绍您的 3 个字符串涵盖的所有 3 个场景:

      第一种情况:s1是直接初始化的例子。直接初始化涵盖了许多不同的场景,您的定义如下:

      使用带括号的非空表达式列表进行初始化。

      这里s1没有class数据类型而是std::string数据类型,所以会进行标准转换,将括号中的数据类型转换为s1的cv-unqualified版本,即const *char . cv-unqualified 表示变量没有附加限定符,例如 (const)(volatile)。请注意,在直接初始化的情况下,它比复制初始化要宽松得多,复制初始化是 s2 的主题。这是因为复制初始化只会引用用户定义的non_explicit(即隐式)的构造函数和转换函数。另一方面,直接初始化考虑隐式和显式构造函数以及用户定义的转换函数。

      接下来,第二个字符串 s2 是复制初始化的一个示例。简而言之,它将值从左侧复制到右侧。这是一个例子:

      当一个非引用类型 T 的命名变量(自动、静态或线程局部)被声明时,初始化器由等号后跟一个表达式组成。

      此方法涵盖的过程是相同的。由于 s2 没有类数据类型而是std::string 数据类型,所以它将使用标准转换将右侧字符串的值转换为左侧const *char 类型的值。但是,如果显式声明函数,则不能像复制初始化器那样进行标准转换,代码编译会失败。

      查看一些与 2 种初始化类型进行比较的代码示例。这应该可以消除上面的任何混淆:

          struct Exp { explicit Exp(const char*) {} }; // This function has an explicit constructor; therefore, we cannot use a copy initialization here
          Exp e1("abc");  // Direct initialization is valid here
          Exp e2 = "abc"; // Error, copy-initialization does not consider explicit constructor
           
          struct Imp { Imp(const char*) {} }; // Here we have an implicit constructor; therefore, a copy initializer can be used
          Imp i1("abc");  // Direct initialization always works
          Imp i2 = "abc"; // Copy initialization works here due to implicit copy constructor 
      

      继续第三种情况,它甚至不是初始化的情况,而是赋值的情况。就像您在 cmets 中所说的那样,变量 s3 使用默认字符串初始化。当您使用等号时,该字符串将替换为“Hello”。这里发生的是,当在string s3; 中声明s3 时,会调用std::string 的默认构造函数,并设置默认字符串值。当您使用 = 符号时,该默认字符串在下一行被替换为 hello

      如果我们在跑步时查看哪个在速度方面更有效,差异是微不足道的。但是,如果我们只这样做,s1 的运行时间会最快:

          int main(void)
          {
              string a("Hello");
          }
      

      编译和运行需要以下时间和内存:

      编译时间:0.32 秒,绝对运行时间:0.14 秒,cpu 时间:0 秒,内存峰值:3 Mb,绝对服务时间:0.46 秒

      如果我们看一下按以下方式编码的字符串 s2:

          int main(void)
          {
              string a = "Hello";
          }
      

      那么程序运行的总时间为:

      编译时间:0.32 秒,绝对运行时间:0.14 秒,cpu 时间:0 秒,内存峰值:3 Mb,绝对服务时间:0.47 秒

      使用复制初始化程序的运行时比直接初始化程序多花费 0.01 秒。差异是存在的,但只是微不足道的。

      s3 的第三种情况,如果编码如下:

          int main(void)
          {
              string a;
              a = "Hello";
          }
      

      总运行、编译时间和空间占用:

      编译时间:0.32 秒,绝对运行时间:0.14 秒,cpu 时间:0 秒,内存峰值:3 Mb,绝对服务时间:0.47 秒

      我想在这里指出一点:第二种和第三种方法之间的运行时差异很可能NOT为零;相反,它的时间差小于 0.01 秒,第三种方法需要更长的时间(s3)。那是因为它有两行代码可以操作;一个是变量的声明,另一个是字符串对变量的赋值。

      希望这能回答你的问题。

      【讨论】:

      • 非常感谢您的详细解答。对于您的性能测试,您应该在循环中多次重复相同的步骤并计算平均时间。我相信这会使性能上的差异更加明显。此外,您必须确保编译器不会简单地将未使用的字符串变量优化掉!
      • 其实@Fabian,我确实运行了多次,但由于时间精确到小数点后 2 位,从我使用的网站来看,时间是完全相同相同!我需要的是能够找到平均值的更精确的时间。尽管如此,还是感谢您的反馈:-)
      【解决方案4】:

      如果一个类没有可访问的拷贝构造函数,则第二种初始化形式无效:

      [temp]$ cat test.cpp
      struct S {
          S(int);
      private:
          S(const S&);
      };
      
      S s1(3);  // okay
      S s2 = 3; // invalid
      [temp]$ clang++ -std=gnu++1z -c test.cpp
      test.cpp:8:3: error: calling a private constructor of class 'S'
      S s2 = 3; // invalid
        ^
      test.cpp:4:5: note: declared private here
          S(const S&);
          ^
      1 error generated.
      [temp]$ 
      

      不同之处在于,第二个在形式上创建了一个S 类型的临时对象,用值3 进行初始化,然后将该临时对象复制到s2。允许编译器跳过副本并直接构造 s2,但仅当副本有效时。

      【讨论】:

      • 我认为copy ctor在这里无关紧要。 melpon.org/wandbox/permlink/2VI5MmtM8qG73qTs
      • @songyuanyao - 我编辑了我的答案以显示编译器输出和开关(与你的匹配)。这仍然是一个错误。
      • 可能是 C++17 问题。来自here,“最后一步通常是优化出来的,转换的结果是直接在为目标对象分配的内存中构造的,但是即使不使用适当的构造函数(移动或复制)也需要可访问.(直到 C++17)”。 C++14C++17
      • @songyuanyao - C++17(还)不是语言定义。即使它成为标准,拥有一个可访问的复制构造函数对于使用当今编译器的人来说也不会“无关紧要”。
      • 很公平。最好在答案中提及潜在的变化。
      猜你喜欢
      • 2014-07-24
      • 2013-11-25
      • 1970-01-01
      • 1970-01-01
      • 2016-06-25
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      • 2012-06-05
      相关资源
      最近更新 更多