[英]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...
}
以下是這種方法的一些主要缺點:
您必須將包含以my_aligned_storage
作為成員的類的標頭也拆分為公共/私有標頭,並為 SDK 保留公共標頭,但將私有標頭包含在 cpp 文件中而不是公共標頭中。 (*)
您已明確控制此類標頭的包含順序,因為可以靜默包含私有標頭而不是公共標頭(反之亦然)。
只有當類型變得完全完整時,您才必須包含帶有 sizeof/alignment 測試斷言的實現,這有時並不總是可能的。
你明確指定了sizeof和alignment,這在不同的上下文中可以不同,例如debug/release、windows/linux、msvc/gcc等等。
(*) 如果my_aligned_storage
的公共和私有標頭無法合並到單個公共聲明標頭中。
在對齊的用戶類實際上並不大並且經常構造/分配/復制的情況下,可以避免或忽略這些缺點,例如內置類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.