简体   繁体   English

为什么 C++ 链接器对 ODR 违规保持沉默?

[英]Why C++ linker is silent about ODR violation?

Let's consider some synthetic but expressive example.让我们考虑一些综合但富有表现力的例子。 Suppose we have Header.h:假设我们有 Header.h:

Header1.h Header1.h

#include <iostream>

// Define generic version
template<typename T>
inline void Foo()
{
    std::cout << "Generic\n";
}

Header2.h Header2.h

void Function1();

Header3.h Header3.h

void Function2();

Source1.cpp源1.cpp

#include "Header1.h"
#include "Header3.h"

// Define specialization 1
template<>
inline void Foo<int>()
{
    std::cout << "Specialization 1\n";
}

void Function1()
{
    Foo<int>();
}

Later I or some else defines similar conversion in another source file.后来我或其他人在另一个源文件中定义了类似的转换。 Source2.cpp源2.cpp

#include "Header1.h"

// Define specialization 2
template<>
inline void Foo<int>()
{
    std::cout << "Specialization 2\n";
}

void Function2()
{
    Foo<int>();
}

main.cpp主.cpp

#include "Header2.h"
#include "Header3.h"

int main()
{
    Function1();
    Function2();
}

The question is what will print Function1() and Function2()?问题是什么将打印 Function1() 和 Function2()? The answer is undefined behavior.答案是未定义的行为。

I expect to see in output: Specialization 1 Specialization 2我希望在输出中看到:专业化 1 专业化 2

But I see: Specialization 2 Specialization 2但我看到:专业化 2 专业化 2

Why C++ compilers are silent about ODR violation?为什么 C++ 编译器对 ODR 违规保持沉默? I would prefer compilation to be failed in this case.在这种情况下,我宁愿编译失败。

I found only one workaround: define template functions in unnamed namespace.我发现只有一种解决方法:在未命名的命名空间中定义模板函数。

The compiler is silent, because it's not required to emit anything by [basic.def.odr/4] :编译器是静默的,因为[basic.def.odr/4]不需要发出任何东西:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement;每个程序都应仅包含一个非内联函数或变量的定义,该函数或变量在该程序中被丢弃的语句之外被 ODR 使用; no diagnostic required.无需诊断。 The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see [class.ctor], [class.dtor] and [class.copy]).该定义可以显式出现在程序中,可以在标准或用户定义的库中找到,或者(在适当的时候)隐式定义(参见 [class.ctor]、[class.dtor] 和 [class.copy ]). An inline function or variable shall be defined in every translation unit in which it is odr-used outside of a discarded statement.内联函数或变量应在每个翻译单元中定义,在其中它在丢弃的语句之外被 odr-使用。

In rare cases it can be useful to violate ODR.在极少数情况下,违反 ODR 可能很有用。

For example, you can instead of std::unique_ptr<MyPimplType> use std::aligned_storage<MyPimplType_sizeof, MyPimplType_alignof> and test the real sizeof and alignment in the constructor/destructor of MyPimplType class.例如,您可以代替std::unique_ptr<MyPimplType>使用std::aligned_storage<MyPimplType_sizeof, MyPimplType_alignof>并在MyPimplType类的构造函数/析构函数中测试真实的 sizeof 和对齐。 This called aligned storage pimpl or something similar.这称为aligned storage pimpl或类似的东西。 Useful when you want to replace impl-by-pointer (by smart pointer to impl) by impl-by-value (aligned storage instead of impl).当您想用 impl-by-value(对齐存储而不是 impl)替换 impl-by-pointer(通过指向 impl 的智能指针)时很有用。

Next, you can create new type of aligned storage, which can test the sizeof and the alignment automatically in it's constructor/destructor:接下来,您可以创建新类型的对齐存储,它可以在其构造函数/析构函数中自动测试 sizeof 和对齐方式:

private/my_aligned_storage_by_decl.hpp or public/my_aligned_storage_by_decl.hpp私人/my_aligned_storage_by_decl.hpp公共/my_aligned_storage_by_decl.hpp

template <typename incomplete_type_to_align, size_t sizeof_, size_t alignof_>
class my_aligned_storage_by { ... };

private/my_aligned_storage_by_impl.hpp私有/my_aligned_storage_by_impl.hpp

// incomplete_type_to_align must be already complete here!
template <typename incomplete_type_to_align, size_t sizeof_, size_t alignof_>
my_aligned_storage_by<...>::my_aligned_storage_by(...) {
    static_assert(sizeof_ == sizeof(incomplete_type_to_align), ...);
    static_assert(alignof_ == std::alignment_of<incomplete_type_to_align>::value, ...);
}

This can be reached only through the ODR violation AND if public and private headers can not be merged to a single header, where the public and private headers having 2 different definitions of the same my_aligned_storage_by class.只能通过 ODR 违规实现,并且如果公共和私有标头不能合并到单个标头,其中公共和私有标头具有相同my_aligned_storage_by类的2 个不同定义

The implementation: https://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/aligned_storage/实施: https ://sourceforge.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/tackle/aligned_storage/
https://github.com/andry81/tacklelib/tree/trunk/include/tacklelib/tackle/aligned_storage/ https://github.com/andry81/tacklelib/tree/trunk/include/tacklelib/tackle/aligned_storage/

Example of usage:使用示例:

include/myheader.hpp包含/myheader.hpp

#include <tacklelib/tackle/aligned_storage/public/aligned_storage_by_decl.hpp>

#define MYCLASS_SIZEOF  ...
#define MYCLASS_ALIGNOF ...

class MyClass
{
  // public methods...
  MyClass(...);

  // replacement as impl-by-value:
  struct This;
  tackle::aligned_storage_by<This,
    MYCLASS_SIZEOF,
    MYCLASS_ALIGNOF,
    tackle::tag_pttn_control_lifetime> m_this;
};

void myfoo(const MyClass & myboo);

src/_impl/myheader_this.hpp src/_impl/myheader_this.hpp

#include <myheader.hpp>

struct MyClass::This
{
  // data members and private function of class MyClass is placed here...
};

src/MyClass.cpp源代码/MyClass.cpp

#include <tacklelib/tackle/aligned_storage/private/aligned_storage_by_decl.hpp>

#include <src/_impl/MyClass_this.hpp>

#include <tacklelib/tackle/aligned_storage/private/aligned_storage_by_impl.hpp>

// public methods implementation...

MyClass::MyClass()
{
  m_this.construct_default();
}

MyClass::MyClass(...)
{
  m_this.construct(This{...});
}

void myfoo(const MyClass & myboo)
{
  auto & realboo = *myboo.m_this.this_();
  // deal with realboo...
}

Here is some major drawbacks of such approach:以下是这种方法的一些主要缺点:

  1. You have to split headers containing classes with my_aligned_storage as members into public/private headers too and leave public headers for SDK, but include private one into cpp files instead of public.您必须将包含以my_aligned_storage作为成员的类的标头也拆分为公共/私有标头,并为 SDK 保留公共标头,但将私有标头包含在 cpp 文件中而不是公共标头中。 (*) (*)

  2. You have explicitly control the order of include of such headers, because private header can be silently included instead of the public (but not vice versa).您已明确控制此类标头的包含顺序,因为可以静默包含私有标头而不是公共标头(反之亦然)。

  3. You have to include implementation with sizeof/alignment test asserts only when the type become fully complete, which is sometimes is not always possible.只有当类型变得完全完整时,您才必须包含带有 sizeof/alignment 测试断言的实现,这有时并不总是可能的。

  4. You have explicitly specify sizeof and alignment, which can be different in different contexts, for example, debug/release, windows/linux, msvc/gcc and so on.你明确指定了sizeof和alignment,这在不同的上下文中可以不同,例如debug/release、windows/linux、msvc/gcc等等。

(*) In case if public and private header of the my_aligned_storage can not be merged into single public declaration header. (*) 如果my_aligned_storage的公共和私有标头无法合并到单个公共声明标头中。

These drawbacks can be avoided or ignored in cases, where an aligned user class is really not big and frequently constructed/assigned/copied, like a builtin type.在对齐的用户类实际上并不大并且经常构造/分配/复制的情况下,可以避免或忽略这些缺点,例如内置类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM