c++ - 用自定义数据类型定义接口(interface):如何保持可读性?

标签 c++ namespaces readability

我正在开发一个有点复杂的库。它提供了许多使用某些自定义DLL_EXPORTstruct ed函数。目前,这些结构是通过使用它们的函数定义的,如下所示:

struct MyDataType
{
  std::wstring name;
  std::wstring purpose;
}

DLL_EXPORT int DoSomething(MyDataType instruction);


出于可维护性的目的,我正在逐渐将该库切换为更标准的界面样式:

struct MyLibraryInterface
{
  virtual int DoSomething(MyDataType instruction) = 0;
}


为了使界面方面的操作更简单,我正在使用第3方库。这个库要求我为我使用的每个自定义数据类型做一些设置工作,而这是我不确定如何进行的地方。我看到一些不同的方法可以做到:

方法一

//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}


优点:


任何需要使用我的库的人都必须#include一个标题
自定义名称空间使数据类型与函数定义分开


缺点:


标头中间的#include看起来很草率和不合适(尽管我已经注意到某些MS标头使用了这种技术)
自定义名称空间确实是必需的,还是我只是让用户感到困惑? (封装本身绝对是必要的,因为我可能会在此过程中更改此数据类型的定义。但是我不知道是否需要自定义名称空间,还是应该将数据类型声明放到结构的声明中,用它。)


方法2

//MyLibraryInterface.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
//3rd-party "paperwork" is directly placed here

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}


优点:


标头中途没有凌乱的#include


缺点:


现在,我的用户将使用的标题中显示了第三方文档(他们不需要看到它们,出于美观/容易理解的原因,我希望他们不希望看到)
感觉好像我根本没有利用数据类型名称空间的功能,因为第3方代码在我的库代码中自由浮动并且没有封装


方法3

//MyLibraryInterface.h
struct MyLibraryInterface_v1
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }

  virtual int DoSomething(MyDataType instruction) = 0;
}

#include "MyLibraryInterface_v1_Types_Backend.private.h" //this header would contain the required "paperwork" to make my datatype work with the 3rd-party lib


优点:


摆脱自定义名称空间
减少了混乱,因为用户现在是指MyLibraryInterface_v1::MyDataType而不是MyLibraryInterface_v1_Types::MyDataType,如果他们仍然在MyLibraryInterface_v1中调用函数,这将更加直观


缺点:


标头最底部的#include看起来非常糟糕
混合数据类型和函数声明对我来说似乎有点


方法4

//MyLibraryInterface_v1_Types.h
namespace MyLibraryInterface_v1_Types
{
  struct MyDataType
  {
    std::wstring name;
    std::wstring purpose;
  }
}
//3rd-party paperwork can be directly placed here, immediately following the definition of the custom datatype


//MyLibraryInterface.h
#include "MyLibraryInterface_v1_Types.h" /* this header, as defined above, holds the definitions of the custom datatypes this library will use. It also includes the 3rd-party paperwork required to make those datatypes work. It can't be a private header, though, because users will need to access it to use the custom types. */

struct MyLibraryInterface_v1
{
  virtual int DoSomething(MyDataType instruction) = 0;
}


优点:


第三方文书直接与需要它的数据类型的声明一起使用


缺点:


用户可能很难找到或使用自定义数据类型
感觉很不直观,因为数据类型同时位于单独的标头和单独的名称空间中




那么哪个最好呢?我是否完全忽略了另一种更好的方法?还是我只是要硬着头皮接受,无论我决定采用哪种方式,我都将面临一些问题。



更新一些更多信息:

我正在使用的第三方库为我将接口包装在struct中。因此,我将能够创建MyLibraryInterface*的对象,第3方库将允许我从指定的DLL访问该接口的实现,然后可以调用MyLibraryObj->DoSomething()。这本质上是pImpl的变体。

这个第三方库还自动包装任何STL类型和任何自定义数据类型,以便可以在多个编译器中使用它们,因此,我的std::wstring用法在这里是完全安全的。但是,该库要求我提供有关如何包装自定义类型的某些设置信息。在定义每种自定义类型之后,我必须在某处提供设置信息,该设置信息禁止在接口标头的顶部放置#include和专用设置信息的“正常”模式。我也不能完全从接口头中删除私有设置信息;通过此接口调用我的库的任何人都必须使用3rd-party库来执行此操作,并且他们需要再次提供接口的声明,以便库知道在给定DLL中要查找的内容。我所能做的就是尝试使私有设置工作看起来尽可能整洁和不干扰,并理想地将其标记为我的图书馆用户永远不需要或不想直接使用的东西。

另外,我可以选择将自定义数据类型放入接口struct或它们自己的namespace中。我起初只是想将它们直接放在struct中,但是由于其中一些数据类型是常量数据(enum class es),​​因此将它们与函数声明一起放在struct中似乎有点草率。 namespace“感觉”清洁器,但缺点是函数和数据类型将被不同地对待(myLibraryObj->DoSomething()MyLibraryInterface_v1_Types::MyDataType),因此可能不如将所有内容都保留在struct中(myLibraryObj->DoSomething(),< cc>)。

最佳答案

如果要创建供他人使用的库,请始终将其包装在名称空间中。使它足够长才能完全描述。如果有人希望在限定名称中使用较短的名称,则可以定义名称空间别名以供自己使用。此外,您不必担心标题内部的外观。如果您在文档方面做得很好,则无需查看标头。

命名空间可以嵌套,您可以使用它来屏蔽(但不能隐藏)实现细节。一种常用的约定是调用这样的命名空间detail。编写说明该名称空间不供公众使用的文档,并包含可能更改的详细信息。

回想一下,#include是纯文本机制,只是将文本块替换为指令。因此,如果在detail名称空间内包含一个外部标头,则它不会出现在全局名称空间或顶级库名称空间中。通过以这种方式包括外部定义,您可以显式地仅从外部标头中公开所需的内容。否则,其他所有内容都被屏蔽在detail中。

下面的示例说明了这些原理。您可以暂停怀疑并假设external_library是在某些外部头文件中定义的。此示例说明了上面概述的每个原理。我假设您需要外部库作为某些类型的定义的一部分;如果不是,则根本不应该在标题中。

namespace library {
    namespace detail {
        #include <whatever>
        namespace external_library {
            class exposed {} ;
            class hidden {} ;
        }
    }
    typedef detail::external_library::exposed external_type ;
    class my_type {} ;
}
library::my_type foo ;
library::external_type bar ;


我没有解决您提出的外部链接问题,因为它们与对您的问题至关重要的范围界定问题是分开的。

关于c++ - 用自定义数据类型定义接口(interface):如何保持可读性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21945906/

相关文章:

c++ - 删除二叉搜索树中的单个元素 (C++)

python - lxml忽略具有未定义命名空间前缀的元素

javascript - 如何在 JavaScript 中将很长的正则表达式拆分为多行?

c++ - 使用boost的timed_wait?

c++ - 正确修复警告 : dereferencing type-punned pointer will break strict-aliasing rules?

Scalatest,不能调用 invokePrivate

c++ - 基于开关访问成员变量的好方法

readability - 感情密码

c++ - openMP 嵌套并行 for 循环与内部并行 for

c++ - rand() 为什么以及如何存在于 cstdlib 的全局和 std 命名空间中?