[英]Using non-member non-friend functions instead of member functions: disadvantages?
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());
}
}
我看到的问题是,当其他人在该项目上工作并且该人将另一个数据成员添加到元数据时,他/她需要记住将此数据成员添加到所有转换函数中。 由于它们位于不同的头文件/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_location
和m_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>>{})
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.