首页 > 解决方案 > 使用非成员非朋友函数而不是成员函数:缺点?

问题描述

Scott Meyers 长期以来一直提倡使用非成员非友元函数而不是成员函数来改进封装。我可以看到这样做的好处。

但是,在我看来,一个缺点是:

我有一些自定义图像类的自定义元数据类,其中包含许多数据成员。有几种格式可以保存图像,并且必须将元数据转换为这些格式可以采用的格式(ENVI、png、TIFF...)。现在我按照 Scott 的建议将这些转换函数放入单独的命名空间中。它们本质上使用公共接口将所有成员复制到适合最终元数据格式的内容中,但它们需要包含所有数据成员。例子:

// file Metadata.h
class Metadata
{
   // Getters
     std::string GetDescription() const;
     std::string GetTimeStamp() const;
     float       GetExposureTimeInMilliSeconds() const;
  // Setters
  // ...
   private:
     std::string m_description;
     std::string m_timeStamp;
     float       m_exposureTimeInMilliSeconds;

   // Added later with associated getters/setters:
   // std::string m_location;
   // std::string m_nameOfPersonWhoTookThePicture;
};

// File UtilityFunctions.h
namespace UtilityFunctions
{
    ENVIMetadata ConvertMetadataToENVIMetadata(const Metadata &i_metadata)\
    {
        ENVIMetadata envi;
        envi.AddMetadata<string>("Description", GetDescription());
        envi.AddMetadata<string>("Time stamp", GetTimeStamp());
        envi.AddMetadata<float>("Exposure time", GetExposureTimeInMilliSeconds());
    }
}

我看到的问题是,当其他人在项目上工作并且该人将另一个数据成员添加到元数据时,他/她需要记住将此数据成员添加到所有转换函数中。由于它们位于不同的 header/cpp 文件中,因此很容易忘记这一点,并且我们有一个不明显的错误,即并非所有数据成员都保存在元数据中。如果该函数是公共成员,则查看头文件(同时添加新数据成员)可能会提醒该人也在其中添加成员,那么完成的必要性只在该文件中。

要点是,使用公共接口确实可以保证(如果接口没有改变),如果类中的某些内容发生变化,基于它的函数将继续工作,但如果向类中添加额外的特性,它并不能保证完整性,这也需要添加到这些功能中。

在某些情况下,有人会建议不要遵循此建议吗?对于这种特定情况,是否有一些范式可以两全其美?

标签: c++oop

解决方案


我不确定我是否一定同意非成员与成员函数的讨论,因为非成员当然不会改善封装。无论如何,我建议在 C++17 中使用结构化绑定来帮助解决这个问题。

// file Metadata.h
struct Metadata
{
     std::string m_description;
     std::string m_timeStamp;
     float       m_exposureTimeInMilliSeconds;

   // Added later:
   // std::string m_location;
   // std::string m_nameOfPersonWhoTookThePicture;
};

// File UtilityFunctions.h
namespace UtilityFunctions
{
    ENVIMetadata ConvertMetadataToENVIMetadata(const Metadata &metadata)
    {
        const auto& [description, timestamp, exposureTimeInMilliSeconds] =
            metadata;
        ENVIMetadata envi;
        envi.AddMetadata<string>("Description", description);
        envi.AddMetadata<string>("Time stamp", timeStamp);
        envi.AddMetadata<float>("Exposure time", exposureTimeInMilliSeconds);
    }
}

稍后添加字段时m_locationm_nameOfPersonWhoTookThePicture结构化绑定声明将产生一个错误,指出您没有提供足够的标识符。


推荐阅读