使用C++ Lambda表达式初始化类成员

4
我想使用 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());
}

3
我认为你不能假设 filePath 初始化时 oneStr 已经被构建了。 - Bill Lynch
1
尝试反转您的成员顺序。 - Amir Kirsh
你期望的输出是什么?" one two" 对于 oneStr 变量? - 김선달
@Soumyajit Roy 我在编译后发布了更新。 - Nick
3个回答

11

我在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) 然后按照类定义中的声明顺序初始化非静态数据成员。

因此,在初始化oneStr之前,filePath已经被初始化。lambda表达式被求值,this被捕获,但this->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"


3

编译后更新

#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,因此没有人知道结果。实际上程序会悄无声息地崩溃。


如果你想让这个例子打印出" one two",可以使用构造函数并在那里初始化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


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接