【问题标题】:Use of c++ lambda expression to initialize a class member使用 c++ lambda 表达式初始化类成员
【发布时间】:2021-01-07 04:51:54
【问题描述】:

我想使用 lambda 表达式初始化一个类成员 (std::string filePath)。 程序编译正常,但没有输出。这里有什么问题?

#include <iostream>
#include <string>

class MyString
{
  public:
  std::string filePath = [this] ()
  {
      return oneStr.append(" two");
  }();
    
  std::string oneStr;
};

int main()
{
  MyString str;
  str.oneStr = std::string(" one");
  printf("%s", str.oneStr.c_str());
}

【问题讨论】:

  • filePath被初始化时,我认为你不能假设oneStr已经被构造了。
  • 尝试颠倒您的成员的顺序
  • 您的预期输出是什么? " one two"oneStr?
  • @Soumyajit Roy 我在编译后发布了更新。

标签: c++ c++11 lambda


【解决方案1】:

我在 Linux 5.8.6 上使用 clang v10.0.1 编译了您的代码。您的代码遇到了核心转储。以下是valgrind 的一些日志(为避免混淆,进行了简化):

==12170== Conditional jump or move depends on uninitialised value(s)
==12170==    at 0x49BEAFE: _M_check_length (basic_string.h:322)
==12170==    by 0x49BEAFE: std::string::append(char const*) (basic_string.h:1238)
==12170==    by 0x10944A: MyString::filePath::{lambda()#1}::operator()[abi:cxx11]() const (test.cc:9)
==12170==    by 0x109390: MyString::MyString() (test.cc:7)
==12170==    by 0x109270: main (test.cc:17)

显然函数append 有问题。事实上你的问题是你在初始化之前使用oneStr

根据cppreference:

https://en.cppreference.com/w/cpp/language/constructor

成员初始化器在列表中的顺序无关:实际初始化顺序如下:

...

3) 然后,按照类定义中的声明顺序初始化非静态数据成员。

因此,filePathoneStr 被初始化之前被初始化。 lambda 被评估,this 被捕获,this-&gt;oneStr 未初始化。


更改声明顺序将修复此未定义行为

#include <iostream>
#include <string>

class MyString
{
  public:
  std::string oneStr;
  std::string filePath = [this] ()
  {
      return oneStr.append(" two");
  }();
};

int main()
{
  MyString str;
  str.oneStr = std::string(" one");
  printf("%s", str.oneStr.c_str());
}

但您仍然可能无法获得预期的结果(可能是 one two?)。您只会看到打印的 one。那是因为 oneStr 先用"" 初始化,然后在MyString::MyString() 中附加" two";但最终在main函数中打印之前分配了" one"

【讨论】:

    【解决方案2】:

    更新一经编译。

    #include <iostream>
    #include <string>
    
    struct MyString{
      std::string filePath = [this] () {
          return oneStr.append(" two");
      }();
        
      std::string oneStr;
    };
    
    int main(){
      MyString str; // 1 
      str.oneStr = std::string(" one"); // 2
      std::cout << str.oneStr << '\n'; // 3
    }
    

    在“1”行创建str 对象。

    filePath 字符串首先被初始化。 Lambda 捕获 this,但 oneStr 尚未初始化,因为已定义为第二个成员。

    Lambda 使用的是oneStr,所以没有人知道结果是什么。实际上程序无声无息地崩溃了。


    如果您希望示例打印“一二”,请执行构造函数并在那里初始化oneStr

    这是工作示例:

    #include <iostream>
    #include <string>
    
    struct MyString{
        MyString(std::string oneStr) : oneStr(std::move(oneStr)){}
    
        std::string oneStr;
    
        std::string filePath = [this] () {
            return oneStr.append(" two");
        }();    
    };
    
    int main(){
        MyString str(" one");
        std::cout << str.oneStr << '\n';
    }
    

    https://gcc.godbolt.org/z/93xbcr

    【讨论】:

      【解决方案3】:

      我不确定您使用的是哪个编译器,但您在 g++ 中的代码会出现分段错误,这是因为当调用 lambda 时 oneStr 不存在。如果您颠倒顺序,它应该可以工作,但请注意 str.oneStr=std::string("one") 行会将“二”替换为一。也许你想要 str.oneStr=str.oneStr+std::string("one")

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-10
        相关资源
        最近更新 更多