一般來說,我想知道這樣的程式是否包含一個從未定義的類的前向宣告,在技術上是否格式正確?
class X;
int main() {}
更具體地說,我想知道是否有這樣的模式
// lib.h
#pragma once
struct X {
private:
friend class F;
};
如果lib.h屬于不包含類定義的共享庫,則可以安全撰寫F,也不依賴于另一個共享庫。
是否有人使用頭檔案最終參考了符號F,這可能會在加載共享庫時導致聯結器錯誤?
uj5u.com熱心網友回復:
根據標準[basic.odr.def]:
每個程式都應包含一個定義,該定義在被丟棄的陳述句(8.5.1)之外在該程式中使用的每個非行內函式或變數;
關鍵部分是odr-used,它由代碼中可能使用該函式的所有其他位置決定。如果它沒有在任何(可能評估的)運算式中命名,則它不是 odr-used 并且不需要定義。
進一步:
如果函式是名稱查找的唯一結果或作為形成該運算式或轉換的一部分而執行的多載決議中一組多載函式(6.5、12.4、12.5)的選定成員,則該函式由運算式或轉換命名,除非它是純虛函式,并且運算式不是使用顯式限定名稱命名函式的 id 運算式,或者運算式形成指向成員的指標 (7.6.2.1)。
宣告函式不需要定義它。呼叫它、獲取它的地址或任何其他需要知道函式位置的運算式(獲取它的地址或呼叫它)都需要函式存在,否則聯結器將無法鏈接這些用途到定義。如果沒有用途,則不依賴于這些符號。
同樣,對于類,同樣的推理也適用。再次,從標準:
要求類的定義在以要求型別別完整的方式使用類的每個背景關系中都是可訪問的。
[示例:以下完整的翻譯單元格式正確,即使它從未定義 X:
struct X; // declare X as a struct type
struct X* x1; // use X in pointer formation
X* x2; // use X in pointer formation
——結束示例]
為了完整起見,標準給出的型別別 T 需要完整的原因:
- 定義了一個 T 型別的物件
- 宣告了型別 T 的非靜態類資料成員
- T 用作 new 運算式中的分配型別或陣列元素型別
- 左值到右值的轉換應用于參考型別 T 物件的左值
- 將運算式(隱式或顯式)轉換為型別 T
- 使用標準轉換、dynamic_cast 或 static_cast 將非空指標常量且型別不是 cv void* 的運算式轉換為指向 T 的型別指標或對 T 的參考
- 將類成員訪問運算子應用于 T 型別的運算式
- typeid 運算子或 sizeof 運算子應用于 T 型別的運算元
- 定義或呼叫具有 T 型別的回傳型別或引數型別的函式
- 定義了具有型別 T 的基類的類
- T 型別的左值分配給
- 型別 T 是 alignof 運算式的主題
- 例外宣告具有型別 T、對 T 的參考或指向 T 的指標
uj5u.com熱心網友回復:
是的,代碼格式正確。
您沒有以需要完整X的方式使用。X僅當您嘗試創建不完整型別的物件、使用成員或任何需要定義的物件時,才會出現編譯器錯誤。
不完整的型別很好,它們只是不……完整。通常,您需要的型別只是宣告,而定義并不重要。
作為一個例子,考慮一個標記型別,一個模板化的型別,它的模板引數只存在于從模板創建不同的型別:
#include <type_traits>
#include <iostream>
template <typename tag>
struct tagged_type {
// nothing in here uses tag
// tag is only there to make tagged_type<X> and tagged_type<Y> different types
};
struct tagA;
struct tagB;
int main() {
using A = tagged_type<tagA>;
using B = tagged_type<tagB>;
std::cout << std::is_same_v<A,B>;
}
這些標簽tagA不需要tagB定義。它們僅用作標記,用于區分A和B。A并且B基本上是相同的型別,但是標簽使它們成為不同的型別。
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/430115.html
上一篇:這個頭檔案在C 中是什么意思?
