C++构造函数:在初始化列表之前初始化局部变量

8

如何在构造函数中存储临时状态(需要初始化列表)(在堆栈上)?

例如,实现以下构造函数...

// configabstraction.h
#include <istream>

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:
    int m_x;
    int m_y;
    int m_z;
};

您是否考虑使用类似这样的有状态的辅助类?

// mysillyparserdontworry.h
#include <json/reader.h> //jsoncpp

class MySillyParserDontWorry
{
public:
    MySillyParserDontWorry(std::istream& input) { input >> m_parseTree; }
    int intByName(const char* name) const { return m_parseTree[name].asInt(); }

private:
    Json::Value m_parseTree;
};

My attempt:

// configabstraction.cpp

ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : local_parserState(input) // init local variable first: Not possible!
    , m_a(local_parserState.intByName("a"))
    , m_b(local_parserState.intByName("b"))
    , m_c(local_parserState.intByName("c"))
{
    MySillyParserDontWorry local_parserState; // ...because it is local
}

你为什么不在构造函数体中直接赋值变量呢? - NathanOliver
@NathanOliver:你不能在函数体内初始化它们。你只能在那里分配它们。在这种情况下是可能的,但如果成员是“const”则不行。 - Christian Hackl
2
“C++ 的人为限制真是太过了!” - 我所知道的每种编程语言都存在这种“人为限制”(除了那些根本没有自定义类型和构造函数的语言)。但这并不妨碍任何人编写出优秀、高效且易读的代码。 - Christian Hackl
@πάνταῥεῖ:不,反射与此无关。只是在Java中,初始化可以推迟到第一次使用,即使您暂时不初始化final(~= const)变量,这种灵活性也会扩展到在构造函数中初始化的字段。(仅为澄清:private final int x = localVariableInConstructor.f();是Java中无法工作的示例。) - Christian Hackl
@πάνταῥεῖ:由于finally结构,这个特性在Java中间接地更有意义,而在C++中则不需要由于析构函数。这一切都归结于不同的编程语言是,嗯,不同的... :) - Christian Hackl
显示剩余4条评论
4个回答

10

使用C++11,您可以通过委托构造函数来解决这个问题:

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:
    ConfigAbstraction(const MySillyParserDontWorry& parser);

    int m_a;
    int m_b;
    int m_c;
};

ConfigAbstraction::ConfigAbstraction(const MySillyParserDontWorry& parser)
    : m_a{parser.intByName("a")}
    , m_b{parser.intByName("b")}
    , m_c{parser.intByName("c")}
{
}

ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : ConfigAbstraction{MySillyParserDontWorry{input}}
{
}

1
你可能想把另一个构造函数设为私有。 - Christian Hackl
@ChristianHackl 是的,我考虑过了,但在这个例子中直接调用它似乎是合理的。 - mattnewport
1
有趣的是...我认为这会使类更难使用,因为它迫使用户做出一个后果难以理解的选择。 - Christian Hackl
@ChristianHackl 没错,我对此并没有特别的偏好,所以我进行了你建议的编辑。 - mattnewport

1
为什么不直接在构造函数中完成任务呢?
ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : m_a(0)
    , m_b(0)
    , m_c(0)
{
    MySillyParserDontWorry local_parserState;
    m_a = local_parserState.intByName("a");
    m_b = local_parserState.intByName("b");
    m_c = local_parserState.intByName("c");
}

有什么具体的要求阻碍了您这样做吗?


C++ 的人工限制真是太不合理了!

这不是一种“人工限制”。在函数范围之外如何初始化局部变量?这只会导致巨大的混乱,即使忽略命名冲突,变量实际上也无法初始化。


欢迎来到糟糕编写的Java构造函数的世界,这只会导致变量实际上被初始化时出现极大的混乱。 - Christian Hackl
@ChristianHackl 嗯,写得不好的代码确实很糟糕。Java是否会编译这样的代码并抛出运行时异常?我对Java不是很熟悉。 - πάντα ῥεῖ
在Java中,你可以编写一个类,像这样:class X { private final int i; X() { int j = 1; i = j; } }。编译器甚至禁止在初始化(或“赋值”)之前使用i。它是100%安全和正确的,但我个人认为这是一个相当令人困惑的特性。也许这也与我的C ++背景有关。 - Christian Hackl
1
如果可以省略初始化程序,就像这种只有普通数据成员的情况,我认为是可以的。但它们通常是不可避免的;我相信编译器会在初始化程序列表中漏掉任何默认构造函数。我更新了问题,以便使用初始化程序列表是目标的一部分。 - user2394284

1
你的问题的另一种解决方案是将三个独立的int打包到一个公共数据结构中。这将允许您使用私有静态助手函数初始化该类型的对象。能够初始化对象而不是稍后分配给它还允许它是const(如果需要的话)。
以下是使用std::tuple的示例。但是,您也可以创建自己的辅助struct甚至是std::array<int, 3>;基本想法仍然相同:只有一个成员对象而不是三个。
#include <istream>
#include <tuple>

class MySillyParserDontWorry
{
public:
    MySillyParserDontWorry(std::istream& input) { /* ... */  }
    int intByName(const char* name) const { return /* ... */ 0; }

};

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:

    static std::tuple<int, int, int> parse(std::istream& input)
    {
        std::tuple<int, int, int> result;
        MySillyParserDontWorry parser(input);
        std::get<0>(result) = parser.intByName("a");
        std::get<1>(result) = parser.intByName("b");
        std::get<2>(result) = parser.intByName("c");
        return result;
    }

    std::tuple<int, int, int> const m;
};


ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : m(parse(input))
{
}

0

你不能在成员变量之前初始化局部变量。这个限制并不是人为的,原因非常简单:

成员变量必须在构造函数体开始之前初始化(构造),因为构造函数体可能会访问它们 - 并且需要为此访问进行初始化。另一方面,在代码进入构造函数体之前,局部变量不存在(对于任何其他函数也是如此)。 结论 - 在成员变量之前初始化局部变量是不可能的。


我相信本地变量的内存是在初始化列表的代码执行之前分配的。我认为C++禁止将此内存用于该代码自身目的是人为的。 - user2394284

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