外联定义成员函数时,哪些限定符必须出现在声明/定义/两者中?

When defining a member function out-of-line, which qualifiers must appear in the declaration / definition / both?

我几乎可以肯定以前有人问过这个问题。不幸的是,我的 C++ 已经生锈了,我什至不知道要搜索什么。

是否有易于记忆的经验法则告诉我哪些限定符(inlinevirtualoverrideconstmutable, 等) 必须 (a) 仅出现在声明中,(b) 仅出现在定义中,(c) 当我在外联定义成员函数时同时出现在声明和定义中?

示例:

struct Geometry {
    …
    virtual Geometry* clone() const = 0;
};

struct Point2 : public Geometry {
    …
    virtual Point2* clone() const override { … }
};

如果我想定义 Point2::clone 外联,反复试验使我相信这将是正确的代码:

struct Point2 : public Geometry {
    …
    virtual Point2* clone() const override;
};

Point2* Point2::clone() const { … }

我不想永远依赖反复试验。但我想明确说明限定符(即重复它们,即使例如它们是由基数 class 暗示的)。是否有一个通用规则,哪个限定符必须准确地去哪里,或者每个规则都不同?

了解在何种情况下必须使用哪些修饰符(或 "specifiers")的最佳方法是了解每个修饰符的含义以及它们的作用。

const 是方法的 "signature" 的一部分。一个签名由什么函数returns、它的参数类型以及它是否是常量方法(const部分)组成。这是无法更改的,必须保持原样。

列表中的其他所有内容 virtualoverride 都与方法的声明相关。它不是方法签名的一部分,只能出现在方法声明中。

唯一的经验法则(如果有的话)是当方法未内联定义时,作为方法签名一部分的任何内容都必须保持不变。如果不是,它必须是声明的一部分,只是(但是,根据每个经验法则,总是有一个例外,即 inline 关键字)。

请注意,默认参数值不被视为方法签名的一部分。默认参数值必须仅在声明中指定。但是,如果方法是内联定义的,则默认参数值最终会作为方法定义的一部分!

一般规则是,当删除限定符会产生不同的函数重载时,该限定符必须出现在两个地方。所有其他限定符保留在声明中。

两个地方都必须出现的三种限定符是const和C++11标准中出现的两种引用限定符:

void foo() const;
void foo() &;
void foo() &&;

所有其他限定符保留在声明中。

我将采用另一种方法:我的经验法则是:将它们放在两个地方,然后按照编译器的指示进行操作。它执行标准规则,它会让你遵守它们。

任何经验法则的问题在于您无法确定它是否适用于特定示例,所以为什么不默认从头开始检查。

如果你很少使用C++,那么学习一些无论如何都不能100%依赖的规则是没有意义的。如果你(开始)经常使用 C++,那么在编译器告诉你几次之后,你自己就会明白要点。

因为这个post和其他的不一样,所以我就一路流氓,给你这个极度未使用的暗角案例例子:constexpr in an explicit instantiation

template <class T>
constexpr auto foo(T)
{
}

template constexpr auto foo(int);
//       ^
// 6 : error: explicit instantiation shall not use 'constexpr' specifier

这不是您问的问题,而是表明您可以将此策略应用于更广泛的类似问题,否则您需要其他经验法则