使用非成员非好友函数代替成员函数:缺点

  • 本文关键字:函数 成员 缺点 好友 c++ oop
  • 更新时间 :
  • 英文 :


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++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时,结构化绑定声明将产生一个错误,说明您没有提供足够的标识符。

您可以提供一个类似元组的Metadata视图,并让转换函数实例化一个std::index_sequence来填充结果;

// file Metadata.h
class Metadata
{
// Getters
std::string GetDescription() const;
std::string GetTimeStamp() const;
float       GetExposureTimeInMilliSeconds() const;
template<size_t I> static const char * name();
// 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;
};
namespace std
{
template<> class tuple_size<Metadata> : public std::integral_constant<std::size_t, 3> {}; // later 5
template<> class tuple_element<0, Metadata>{ using type = std::string; };
template<> class tuple_element<1, Metadata>{ using type = std::string; };
template<> class tuple_element<2, Metadata>{ using type = float; };
/* Later add
template<> class tuple_element<3, Metadata>{ using type = std::string; };
template<> class tuple_element<4, Metadata>{ using type = std::string; };
*/
}
template<size_t I> std::tuple_element_t<I, Metadata> get(const Metadata & meta);
template<> std::string get<0>(const Metadata & meta) { return meta.GetDescription(); }
template<> std::string get<1>(const Metadata & meta) { return meta.GetTimeStamp(); }
template<> float get<2>(const Metadata & meta) { return meta.GetExposureTimeInMilliSeconds(); }
/* Later add
template<> std::string get<3>(const Metadata & meta) { return meta.GetLocation(); }
template<> std::string get<4>(const Metadata & meta) { return meta.GetPhotographerName(); }
*/
template<> const char * Metadata::name<0>() { return "Description"; }
template<> const char * Metadata::name<1>() { return "Time Stamp"; }
template<> const char * Metadata::name<2>() { return "Exposure Time"; }
/* Later add
template<> const char * Metadata::name<3>() { return "Location"; }
template<> const char * Metadata::name<2>() { return "PhotographerName"; }
*/

当你添加成员时,转换功能不会改变

// File UtilityFunctions.h
namespace UtilityFunctions
{
namespace detail
{
template<size_t... Is>
ENVIMetadata ConvertMetadataToENVIMetadata(const Metadata &i_metadata, std::index_sequence<Is...>)
{
ENVIMetadata envi;
envi.AddMetadata<std::tuple_element_t<Is, Metadata>>(Metadata::name<Is>(), get<Is>(i_metadata))...;
return envi;
}
}
ENVIMetadata ConvertMetadataToENVIMetadata(const Metadata &i_metadata)
{
return detail::ConvertMetadataToENVIMetadata(i_metadata, std::make_index_sequence<std::tuple_size_v<Metadata>>{})
}
}