簡體   English   中英

為什么 C++ 鏈接器對 ODR 違規保持沉默?

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

讓我們考慮一些綜合但富有表現力的例子。 假設我們有 Header.h:

Header1.h

#include <iostream>

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

Header2.h

void Function1();

Header3.h

void Function2();

源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>();
}

后來我或其他人在另一個源文件中定義了類似的轉換。 源2.cpp

#include "Header1.h"

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

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

主.cpp

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

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

問題是什么將打印 Function1() 和 Function2()? 答案是未定義的行為。

我希望在輸出中看到:專業化 1 專業化 2

但我看到:專業化 2 專業化 2

為什么 C++ 編譯器對 ODR 違規保持沉默? 在這種情況下,我寧願編譯失敗。

我發現只有一種解決方法:在未命名的命名空間中定義模板函數。

編譯器是靜默的,因為[basic.def.odr/4]不需要發出任何東西:

每個程序都應僅包含一個非內聯函數或變量的定義,該函數或變量在該程序中被丟棄的語句之外被 ODR 使用; 無需診斷。 該定義可以顯式出現在程序中,可以在標准或用戶定義的庫中找到,或者(在適當的時候)隱式定義(參見 [class.ctor]、[class.dtor] 和 [class.copy ]). 內聯函數或變量應在每個翻譯單元中定義,在其中它在丟棄的語句之外被 odr-使用。

在極少數情況下,違反 ODR 可能很有用。

例如,您可以代替std::unique_ptr<MyPimplType>使用std::aligned_storage<MyPimplType_sizeof, MyPimplType_alignof>並在MyPimplType類的構造函數/析構函數中測試真實的 sizeof 和對齊。 這稱為aligned storage pimpl或類似的東西。 當您想用 impl-by-value(對齊存儲而不是 impl)替換 impl-by-pointer(通過指向 impl 的智能指針)時很有用。

接下來,您可以創建新類型的對齊存儲,它可以在其構造函數/析構函數中自動測試 sizeof 和對齊方式:

私人/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 { ... };

私有/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, ...);
}

只能通過 ODR 違規實現,並且如果公共和私有標頭不能合並到單個標頭,其中公共和私有標頭具有相同my_aligned_storage_by類的2 個不同定義

實施: 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/

使用示例:

包含/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

#include <myheader.hpp>

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

源代碼/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...
}

以下是這種方法的一些主要缺點:

  1. 您必須將包含以my_aligned_storage作為成員的類的標頭也拆分為公共/私有標頭,並為 SDK 保留公共標頭,但將私有標頭包含在 cpp 文件中而不是公共標頭中。 (*)

  2. 您已明確控制此類標頭的包含順序,因為可以靜默包含私有標頭而不是公共標頭(反之亦然)。

  3. 只有當類型變得完全完整時,您才必須包含帶有 sizeof/alignment 測試斷言的實現,這有時並不總是可能的。

  4. 你明確指定了sizeof和alignment,這在不同的上下文中可以不同,例如debug/release、windows/linux、msvc/gcc等等。

(*) 如果my_aligned_storage的公共和私有標頭無法合並到單個公共聲明標頭中。

在對齊的用戶類實際上並不大並且經常構造/分配/復制的情況下,可以避免或忽略這些缺點,例如內置類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM