3 回答

TA貢獻1830條經驗 獲得超3個贊
簡短的答案是,它們使鏈接程序保持相同,但代價是使編譯器比以前更加復雜。
即,除了導致鏈接器要定義多個定義之外,它仍然僅生成一個定義,而編譯器必須對其進行分類。
這也導致程序員要整理出一些更復雜的規則,但是它非常簡單,所以沒什么大不了的。當您為單個成員指定了兩個不同的初始化程序時,就會出現額外的規則:
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
};
現在,這時的額外規則處理a使用非默認構造函數時用于初始化的值。答案很簡單:如果使用未指定任何其他值的構造函數,1234則將使用初始化a-但是,如果使用指定其他值的構造函數,1234則基本上忽略。
例如:
#include <iostream>
class X {
int a = 1234;
public:
X() = default;
X(int z) : a(z) {}
friend std::ostream &operator<<(std::ostream &os, X const &x) {
return os << x.a;
}
};
int main() {
X x;
X y{5678};
std::cout << x << "\n" << y;
return 0;
}
結果:
1234
5678

TA貢獻1712條經驗 獲得超3個贊
我猜想推理可能是在模板完成之前編寫的。對于C ++ 11支持模板的靜態成員而言,對于靜態成員的類內初始化器來說,所有必需的“復雜鏈接器規則”已經存在。
考慮
struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
// thanks @Kapil for pointing that out
// vs.
template <class T>
struct B { static int s; }
template <class T>
int B<T>::s = ::ComputeSomething();
// or
template <class T>
void Foo()
{
static int s = ::ComputeSomething();
s++;
std::cout << s << "\n";
}
在所有三種情況下,編譯器的問題都是相同的:它應在哪個轉換單元中發出的定義s以及對其進行初始化所需的代碼?一種簡單的解決方案是將其發送到任何地方,然后讓鏈接程序對其進行整理。這就是鏈接器已經支持諸如之類的原因的原因__declspec(selectany)。沒有它,就不可能實現C ++ 03。這就是為什么沒有必要擴展鏈接器的原因。
坦率地說:我認為舊標準中的推理完全是錯誤的。
更新
正如Kapil指出的那樣,當前標準(C ++ 14)甚至不允許我的第一個示例。無論如何,我還是保留了它,因為IMO是實現過程中最困難的情況(編譯器,鏈接器)。我的觀點是:即使是這種情況,也沒有比例如使用模板時所允許的情況難。

TA貢獻1785條經驗 獲得超8個贊
從理論上講,So why do these inconvenient restrictions exist?...原因是有效的,但可以輕松地繞開它,而這正是C ++ 11所做的。
當你有一個文件,它只是包括文件和忽略任何初始化。僅在實例化類時才初始化成員。
換句話說,初始化仍然與構造函數聯系在一起,只是表示法有所不同并且更加方便。如果未調用構造函數,則不會初始化值。
如果調用了構造函數,則使用類內初始化(如果存在)初始化值,否則構造函數可以使用自己的初始化覆蓋它們。初始化的路徑本質上是相同的,即通過構造函數。
從Stroustrup自己在C ++ 11上的常見問題中可以明顯看出這一點。
- 3 回答
- 0 關注
- 411 瀏覽
添加回答
舉報