构造函数中调用的代码比 lambda 更简单,该构造函数使用输出参数函数来初始化 const 成员

Posted

技术标签:

【中文标题】构造函数中调用的代码比 lambda 更简单,该构造函数使用输出参数函数来初始化 const 成员【英文标题】:Code simpler than lambda for a call in constructor that uses an output parameter function for initializing a const member 【发布时间】:2015-09-13 16:53:53 【问题描述】:

在标题中,我有

class CSomeClass

    const GUID m_guid;

public:
    CSomeClass();
///...

并且在源文件中

CSomeClass::CSomeClass()
    , m_guid(
        []() 
        GUID g;
        ::CoCreateGuid(&g);
        return g;
        ()
    )


如您所知,Guid 可用作不打算更改的标识。鉴于::CocreateGuid() 函数提供了我想要的输出参数,而不是返回它,我不能直接使用对该函数的简单调用来初始化 m_guid 成员字段,即常量。

因此,它的常量性的结果是,它必须在初始化列表中的左括号之前初始化,因此不能简单地通过在构造函数主体中调用 ::CocreateGuid() 来分配。

有没有比这个 lambda 表达式更简单的方法来初始化它?

【问题讨论】:

@IgorTandetnik:你认为我没有尝试你所说的吗?试试看。 我的第一反应是“好悲伤”。只是不要这样做。在构造函数中初始化它,而不是在初始化列表中。帮您自己或任何必须阅读和维护您的代码的人提供帮助。 你仍然可以写一个真正的实用函数。 对。没关系。 const 已经在构造函数主体中起作用。我和@Jarod42 在一起——写private: static GUID CSomeClass::MakeGUID() 并用它来初始化m_guid 我也支持@Jarod42 和@IgorTandetnik 的实用功能。但是,我将把它完全隐藏在源文件 (.cpp) 中,而不是 private static 成员函数。将其设置为 .cpp 中的 static 或将其置于 .cpp 中的匿名 namespace 中。 【参考方案1】:

当 lambda 表达式正确时,我会为此使用辅助函数:

GUID create_guid()

    GUID g;
    ::CoCreateGuid(&g);
    return g;


CSomeClass::CSomeClass() : m_guid(create_guid()) 

此外,create_guid() 本身具有含义并且可以重复使用(即使将其作为实现细节是可能的/正确的)。

【讨论】:

我最终创建了一个这样的全局函数,因为它最适合重用目的。【参考方案2】:

您应该考虑将 GUID 包装在它自己的类中:

class CGUID

public:
    CGUID()
    
        CoCreateGuid(m_guid);
    

    const GUID& guid() const  return m_guid; 
    // Maybe some useful functions:
    bool operator==(const CGUID&) const;

private:
    GUID m_guid;
;

现在你可以使用上面的成员了:

class CSomeClass

    const CGUID m_guid;
...

【讨论】:

【参考方案3】:

在这里我们抽象出你的模式:

template<class A>
A co_make( HRESULT(*f)(A*) 
  A a;
  HRESULT hr = f(&a);
  Assert(SUCCEEDED(hr));
  if (!SUCCEEDED(hr))
    throw hr;
  return a;


CSomeClass::CSomeClass()
  m_guid(
    co_make(&::CoCreateGuid)
  )

我们检测失败并断言,如果是这种情况,则抛出。

我不确定这是否更简单。

真的,写一个GUID make_guid() 函数,把它放在一些头文件中,然后调用它。

【讨论】:

在我看来,我确信这并不简单。期间。【参考方案4】:

您的提议是初始化常量实例成员的最简单方法。

不要害怕 lambda,事实上,一般来说,将 lambda 用于常量和引用的复杂初始化是一种新的风格推荐,因为它们共享仅在声明点初始化的属性 (或初始化器列表中的实例成员初始化)。

此外,您的代码触发了“命名返回值优化”,并且在从 lambda 返回时没有复制构造。

CoCreateGuid 的接口有缺陷,因为它需要一个输出参数。

如果您坚持不使用 lambda,我认为下一个最实用的替代方法是在构造函数主体中,使用 const_cast 去约束以将其传递给 CoCreateGuid。

请注意,当您输入构造函数的主体时,该语言认为所有单独的成员都已正确初始化,并且如果发生异常,将为它们调用析构函数,这对于在初始化程序中是否进行初始化有很大的不同列出或留下垃圾的二进制模式。

最后,不幸的是,您不能只在 lambda 中使用对 m_guid 的去约束引用来调用 CoCreateGuid,因为 lambda 仍将返回一个值,这将覆盖该成员。和你已经写的基本一样(除了g的默认构造函数)

【讨论】:

关于您的建议“使用 const_cast 去约束”可能会起作用,但这实际上会产生按照 7.6.1/4 的未定义行为:“在其生命周期 (3.8) 期间修改 const 对象的任何尝试都会导致未定义的行为。” @CassioNeri 你是对的,我只是想强调一下,我并不是在提倡去约束,我只是说如果代码不想在唯一的地方初始化常量这样做是合法的,最实际的非法方法是如图所示 函数将其结果作为输出参数,并不意味着其接口已损坏。在这种情况下,具体来说,它是一个 COM 调用(通常它们以 ::Co 开头,第三个字母为大写),并且它们都遵循返回 HRESULT 值的约定,以便谁使用它们来了解操作的进行情况。想象一下,您需要一个返回 3 个不同实体的函数。您无法通过正常退货方式退货,因为它只能退货一个。但是输出参数可以随心所欲。 @serg 元组不同意。 我没有使用std::tuple的经验,但是当我有机会的时候,我想我会尝试一下。【参考方案5】:

如果将m_guid 声明为mutable 实例成员而不是const 会更简单。不同之处在于mutable 就像一个类用户的const,但在类中是一个完美的左值

【讨论】:

m_guid 在 CSomeClass 创建后不应该更改,即使在 CSomeClass 本身中也是如此。所以我没有看到你的方法有任何优势。 @sergiol m_guid 是否永远不会改变是 CSomeClass 的不变量的一部分,CSomeClass 的成员是保持不变量的成员,因此,在构造函数的主体中初始化它是完全合法的m_guid 并且不再更改它。世界其他地方将看到一个常数。

以上是关于构造函数中调用的代码比 lambda 更简单,该构造函数使用输出参数函数来初始化 const 成员的主要内容,如果未能解决你的问题,请参考以下文章

C#基础构造函数:最熟悉的陌生人

lambda 比 python 中的函数调用慢,为啥

编译器如何实现lambda表达式?

Java中的构造函数引用和方法引用

java8新特性→方法和构造函数引用:替代Lambda表达式

Java8新特性之方法引用