C++ module編程升級(jí)指南,子模塊與分區(qū)全解析
子模塊
1.C++ 標(biāo)準(zhǔn)與子模塊
C++ 標(biāo)準(zhǔn)并沒有特別提到子模塊,但允許在模塊名稱中使用點(diǎn)(.),從而可以按任何你想要的層次結(jié)構(gòu)來組織模塊。例如,以下是一個(gè) DataModel 命名空間的示例:
export module datamodel;
import <vector>;
export namespace DataModel {
class Person { /* ... */ };
class Address { /* ... */ };
using Persons = std::vector<Person>;
}
Person 和 Address 類都在 DataModel 命名空間內(nèi),也在 datamodel 模塊中??梢酝ㄟ^定義兩個(gè)子模塊來重構(gòu):datamodel.person 和 datamodel.address。
2.子模塊的模塊接口文件
datamodel.person 子模塊的模塊接口文件如下:
export module datamodel.person; // datamodel.person 子模塊
export namespace DataModel {
class Person { /* ... */ };
}
datamodel.address 子模塊的模塊接口文件如下:
export module datamodel.address; // datamodel.address 子模塊
export namespace DataModel {
class Address { /* ... */ };
}
最后,定義一個(gè) datamodel 模塊如下。它導(dǎo)入并立即導(dǎo)出兩個(gè)子模塊。
export module datamodel; // datamodel 模塊
export import datamodel.person; // 導(dǎo)入并導(dǎo)出 person 子模塊
export import datamodel.address; // 導(dǎo)入并導(dǎo)出 address 子模塊
import <vector>;
export namespace DataModel {
using Persons = std::vector<Person>;
}
當(dāng)然,子模塊中類的方法實(shí)現(xiàn)也可以放在模塊實(shí)現(xiàn)文件中。例如,假設(shè) Address 類有一個(gè)默認(rèn)構(gòu)造函數(shù),僅打印一條語句到標(biāo)準(zhǔn)輸出;該實(shí)現(xiàn)可以放在一個(gè)名為 datamodel.address.cpp 的文件中:
module datamodel.address; // datamodel.address 子模塊
import <iostream>;
using namespace std;
DataModel::Address::Address() {
cout << "Address::Address()" << endl;
}
3.使用子模塊的好處
用子模塊結(jié)構(gòu)化代碼的好處是,客戶端可以導(dǎo)入他們想要使用的特定部分,或者一次性導(dǎo)入所有內(nèi)容。例如,如果客戶端僅對(duì)使用 Address 類感興趣,以下導(dǎo)入聲明就足夠了:
import datamodel.address;
另一方面,如果客戶端代碼需要訪問 datamodel 模塊中的所有內(nèi)容,那么以下導(dǎo)入聲明是最簡單的:
import datamodel;
模塊分區(qū)
1.分區(qū)與子模塊的區(qū)別
分區(qū)和子模塊之間的區(qū)別在于,子模塊結(jié)構(gòu)對(duì)模塊的使用者是可見的,允許用戶選擇性地只導(dǎo)入他們想使用的子模塊。另一方面,分區(qū)用于內(nèi)部結(jié)構(gòu)化模塊,對(duì)模塊的使用者不可見。在模塊接口分區(qū)文件中聲明的所有分區(qū)最終必須由主要的模塊接口文件導(dǎo)出。一個(gè)模塊始終只有一個(gè)這樣的主模塊接口文件,即包含 export module 名稱聲明的接口文件。
2.創(chuàng)建模塊分區(qū)
模塊分區(qū)是通過將模塊名稱和分區(qū)名稱用冒號(hào)分隔來創(chuàng)建的。分區(qū)名稱可以是任何合法的標(biāo)識(shí)符。例如,前一節(jié)中的 DataModel 模塊可以使用分區(qū)而不是子模塊來重構(gòu)。以下是 datamodel.person.cppm 模塊接口分區(qū)文件中的 person 分區(qū):
export module datamodel:person; // datamodel:person 分區(qū)
export namespace DataModel {
class Person { /* ... */ };
}
3.分區(qū)的實(shí)現(xiàn)文件注意事項(xiàng)
使用分區(qū)時(shí)的一個(gè)注意事項(xiàng)是:與分區(qū)相結(jié)合的實(shí)現(xiàn)文件只能有一個(gè)文件具有特定的分區(qū)名稱。因此,以下聲明開始的實(shí)現(xiàn)文件是不正確的:
module datamodel:address;
相反,你可以將 address 分區(qū)的實(shí)現(xiàn)放在 datamodel 模塊的實(shí)現(xiàn)文件中:
module datamodel; // 不是 datamodel:address!
import <iostream>;
using namespace std;
DataModel::Address::Address() {
cout << "Address::Address()" << endl;
}
警告:多個(gè)文件不能有相同的分區(qū)名稱。因此,擁有多個(gè)具有相同分區(qū)名稱的模塊接口分區(qū)文件是非法的,且分區(qū)文件中聲明的實(shí)現(xiàn)不能放在具有相同分區(qū)名稱的實(shí)現(xiàn)文件中。相反,應(yīng)該將這些實(shí)現(xiàn)放在模塊的實(shí)現(xiàn)文件中。
4.編寫分區(qū)模塊的要點(diǎn)
編寫分區(qū)結(jié)構(gòu)的模塊時(shí),要記住的重要一點(diǎn)是,每個(gè)模塊接口分區(qū)最終必須由主模塊接口文件直接或間接導(dǎo)出。要導(dǎo)入分區(qū),只需指定分區(qū)名稱,前綴為冒號(hào),例如 import :person。說 import datamodel:person 是非法的。請(qǐng)記住,分區(qū)對(duì)模塊的使用者不可見;分區(qū)只在模塊內(nèi)部結(jié)構(gòu)化。因此,用戶不能導(dǎo)入特定的分區(qū);他們必須導(dǎo)入整個(gè)模塊。分區(qū)只能在模塊內(nèi)部導(dǎo)入,因此在冒號(hào)前指定模塊名稱是多余的(且非法的)。
以下是 datamodel 模塊的主模塊接口文件:
export module datamodel; // datamodel 模塊(主模塊接口文件)
export import :person; // 導(dǎo)入并導(dǎo)出 person 分區(qū)
export import :address; // 導(dǎo)入并導(dǎo)出 address 分區(qū)
import <vector>;
export namespace DataModel {
using Persons = std::vector<Person>;
}
5.使用分區(qū)結(jié)構(gòu)化的 datamodel 模塊
import datamodel;
int main() {
DataModel::Address a;
}
注意:分區(qū)用于內(nèi)部結(jié)構(gòu)化模塊。分區(qū)在模塊外部不可見。因此,模塊的用戶不能導(dǎo)入特定分區(qū);他們必須導(dǎo)入整個(gè)模塊。早先提到,模塊名稱聲明隱含地包含一個(gè)導(dǎo)入名稱聲明。但對(duì)于分區(qū),情況并非如此。例如,datamodel:person 分區(qū)沒有隱含的 import datamodel 聲明。在這個(gè)例子中,甚至不允許在 datamodel:person 接口分區(qū)文件中添加顯式的 import datamodel 聲明。這樣做會(huì)導(dǎo)致循環(huán)依賴:datamodel 接口文件包含 import :person 聲明,而 datamodel:person 接口分區(qū)文件會(huì)包含 import datamodel 聲明。
實(shí)現(xiàn)分區(qū)
1.定義和用途
實(shí)現(xiàn)分區(qū)不需要在模塊接口分區(qū)文件中聲明,它也可以在模塊實(shí)現(xiàn)分區(qū)文件中聲明,這是一個(gè)帶有 .cpp 擴(kuò)展名的普通源代碼文件,在這種情況下,它是一個(gè)實(shí)現(xiàn)分區(qū),有時(shí)也稱為內(nèi)部分區(qū)。這種分區(qū)不會(huì)被主模塊接口文件導(dǎo)出。例如,假設(shè)你有以下數(shù)學(xué)主模塊接口文件(math.cppm):
export module math; // math 模塊聲明
export namespace Math {
double superLog(double z, double b);
double lerchZeta(double lambda, double alpha, double s);
}
假設(shè)進(jìn)一步數(shù)學(xué)函數(shù)的實(shí)現(xiàn)需要一些不能被模塊導(dǎo)出的輔助函數(shù)。實(shí)現(xiàn)分區(qū)是放置這些輔助函數(shù)的完美位置。以下在名為 math_helpers.cpp 的文件中定義了這樣的實(shí)現(xiàn)分區(qū):
module math:details; // math:details 實(shí)現(xiàn)分區(qū)
double someHelperFunction(double a) {
return /* ... */;
}
2.實(shí)現(xiàn)分區(qū)的訪問
其他數(shù)學(xué)模塊實(shí)現(xiàn)文件可以通過導(dǎo)入這個(gè)實(shí)現(xiàn)分區(qū)來訪問這些輔助函數(shù)。例如,一個(gè)數(shù)學(xué)模塊實(shí)現(xiàn)文件(math.cpp)可能看起來像這樣:
module math;
import :details;
double Math::superLog(double z, double b) {
return /* ... */;
}
double Math::lerchZeta(double lambda, double alpha, double s) {
return /* ... */;
}
當(dāng)然,使用帶有輔助函數(shù)的這種實(shí)現(xiàn)分區(qū)只有在多個(gè)其他源文件使用這些輔助函數(shù)時(shí)才有意義。