自学内容网 自学内容网

C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(2)

4、模块实现文件

        模块可以被分割为一个模块接口文件与一个或多个模块实现文件。模块实现文件通过以.cpp结尾。可以自由决定哪个实现移到模块实现文件,哪个实现留在模块接口文件。一个选择是移动所有函数与成员函数实现到模块实现文件,只留下函数原型,类定义等等在模块接口文件。另一个选项是留下小的函数与成员函数在接口文件,而移动其它函数与成员函数到实现文件。灵活性自己掌握就可以了。

        模块实现文件也包含一个命名模块声明来指定实现了哪个模块,但是没有export关键字。例如,前面person模块可以分割为如下的一个接口与一个实现文件,下面是模块接口文件:

export module person;

import std;

export class Person
{
public:
explicit Person(std::string firstName, std::string lastName);

const std::string& getFirstName() const;
const std::string& getLastName() const;

private:
std::string m_firstName;
std::string m_lastName;
};

        实现放到了Person.cpp模块实现文件中:

module person;

using namespace std;

Person::Person(string firstName, string lastName)
: m_firstName{ move(firstName) }, m_lastName{ move(lastName) }
{
}

const string& Person::getFirstName() const
{
return m_firstName;
}

const string& Person::getLastName() const
{
return m_lastName;
}

        注意实现文件没有person模块的导入声明。module person声明隐式包含了import person声明。也要注意实现文件没有std的任何导入声明,即使在成员函数的实现中使用了std::string。感谢隐式import person,并且因为该实现文件是同一person模块的一部分,它隐式地继承了模块接口文件中的std导入声明。与之相对的是,添加import person声明到test.cpp文件并没有隐式继承std导入声明,因为test.cpp不是person模块的一部分。关于这些内容还可以讲很多,后面再讨论吧。

        注意:所有模块中与模块实现中的导入声明必须在文件的头部,在命名模块声明之后,但是在其它声明之前。

        警告:模块实现文件不能导出任何东东;只有模块接口文件可以。

5、从实现中分割接口

        当使用头文件而不是模块时,强烈推荐只将声明放到头文件(.h)中,将所有实现放到源文件(.cpp)中。一个原因是提升编译时间。如果将实现放到了头文件中,任何改变,哪怕只是一个注释,都会要求重新编译所有其它包含了这个头文件的源文件。对于特定的头文件,这可能会引起整个代码基础的连环反应,产生对整个程序的全编译。而将实现放到源文件中,对实现进行修改而不触动头文件意味着只有单个源文件需要被重新编译。

        模块以不同的方式工作。模块文件只包含类定义,文件原型,等等,但是不包含任何函数或成员函数的实现,即使这些实现就在接口文件中。这意味着修改在模块接口文件中的一个函数或成员函数实现不会要求重新编译使用那个模块的用户,只要不涉及接口部分,例如,函数头(=函数名,参数列表,与返回类型)。两个例外是用inline关键字标示的函数与模板定义。对于这两个,编译器需要知道在客户代码使用它们时编译时其完整的实现。因此,任何对内联函数或模板定义的修改都会触发客户代码的重新编译。

        注意:当在头文件中的类定义包含成员函数实现时,这些成员函数是隐式内联的,即使没有用inline关键字标注。这个结论在模块接口文件的类定义的成员函数实现中是不正确的。如果它们需要是内联的,就需要显式地标注。

        即使在技术上,不再要求将接口从实现中分割,在某些场景下,还是推荐这样做。主要目的是有清晰易读的接口。函数实现可以呆丰接口中,只要它们不妨碍接口并且使得用户快速掌握公共接口提供了什么更困难就行。例如,如果模块有一个很大的公共接口,可能不要用实现来妨碍接口就会好一些,这样用户可以有一个提供了什么的整体更好的观感。还有,小的getter与setter函数可以呆在接口文件中,只要它们不影响接口的可读性就好。

        将接口从实现中分割开可以通过几种方式来实现。一个选项是将接口与实现文件分开,如前面章节讨论的那样。另外一个选项是在一个单独的模块接口文件中分割接口与实现。例如,下面是Person类定义在一个单独的模块接口文件中(person.cppm),但是带有从接口分割来的实现:

export module person;

import std;

// Class definition
export class Person
{
public:
explicit Person(std::string firstName, std::string lastName);

const std::string& getFirstName() const;
const std::string& getLastName() const;

private:
std::string m_firstName;
std::string m_lastName;
};

// Implementations
Person::Person(std::string firstName, std::string lastName)
: m_firstName{ std::move(firstName) }, m_lastName{ std::move(lastName) }
{
}

const std::string& Person::getFirstName() const
{
return m_firstName;
}

const std::string& Person::getLastName() const
{
return m_lastName;
}


原文地址:https://blog.csdn.net/weixin_71738303/article/details/143460494

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!