header 中定义的(成员)函数是否可以根据包含的位置有不同的定义?这个定义好吗?

Can a (member) function defined in a header have different definition depending on where it is included? Is this well defined?

假设我在图书馆里有这样的东西 header:

// ExampleLib.h

#ifndef DEFAULT_OPTION_VALUE
#  define DEFAULT_OPTION_VALUE 1
#endif

class Example {
public:
    void doSomething ();
private:
    static bool getDefaultOptionValue () {
        return (0 != DEFAULT_OPTION_VALUE);
    } 
};

库源文件中的类似内容:

#include <ExampleLib.h>

void Example::doSomething () {
    ...
    if (getDefaultOptionValue()) {
       ...
    } else {
       ...
    }
    ...
}

现在,假设当一个链接到这个库的应用程序被构建时,它 #defines 通过编译器命令行或其他任何方式 DEFAULT_OPTION_VALUE 自己的值(可能与值集不同编译库时),预期效果是允许应用程序在编译时确定 doSomething() 的行为。

我的主要问题是:这是否有效(并且会按预期运行),还是未定义的行为?

那么,如果它 well-defined,编译器在编译时是否存在任何风险?优化掉改变#define的效果,比如:

因此,该行为不会反映在构建应用程序本身时设置的 DEFAULT_OPTION_VALUE 值?

不确定这是否重要,但假设是 C++14 或更高版本。

[basic.def.odr]/6 There can be more than one definition of a class type ... in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named D defined in more than one translation unit, then

(6.1) — each definition of D shall consist of the same sequence of tokens...
...

If the definitions of D do not satisfy these requirements, then the behavior is undefined.

如果 DEFAULT_OPTION_VALUE 在包含 Example 定义的两个翻译单元中扩展为不同的标记序列,那么组合这些翻译单元的程序将表现出未定义的行为。

作为文本替换,DEFAULT_OPTION_VALUE 名称的 link 没有任何保留。预处理器将源文件更改为:

return (0 != 1);

编译库时,它会看到该定义。

当在主项目中使用相同的头文件,但 DEFAULT_OPTION_VALUE 的定义不同时,它会在该翻译单元 中编译为不同的函数体

如果这是 link 使用普通库(目标文件的集合)编辑的,这显然是未定义的行为。我确实希望编译器会在每个使用它的地方内联函数,所以它不能为整个程序做 linker-choose-one。我还希望它确实能优化语句,因为在编译时已知常量值。

Is this undefined behavior

根据 Igor 中引用的规则,这是 ODR 违规。这种违反使程序格式错误(不需要诊断)。这实际上与拥有 UB 相同。

and, if so, can I fix it?

您可以使用非成员函数,它允许您声明带有内部链接的函数。如果你这样做,那么每个翻译单元都会有一个相同的 class 声明,并且每个都会声明一个单独的函数,因此声明不同不会成为问题。令人困惑的是,声明具有内部链接的函数的关键字与用于静态成员函数的关键字相同。您只需在 class:

之外声明它
namespace detail {
    static bool getDefaultOptionValue () {
        return (0 != DEFAULT_OPTION_VALUE);
    }
}

class Example;

因为你的成员函数是私有的,我把函数放在一个命名空间中 detail,这是表达“这是私有的,这里是龙”的传统方式。