继承C++中的结构

Inheriting Struct in C++

本文关键字:结构 C++ 继承      更新时间:2023-10-16

我有一个类,Table,其中包含一个成员heightheight的值应为 int 或指向具有附加数据的对象。

这里的目的是用户可以输入一个直接值,也可以选择一个预定义的值(由其 ID 标识(,该值可以由多个Table对象共享。

这是我认为可以实现此功能的一种方式。

1( 创建一个基类Height

2(有两个子类StraightHeightPredefinedHeight

StraightHeight将只包含一个带有高度的整数(带有 getter 和 setter(,PredefinedHeight将包含其他数据:

class PredefinedHeight : public Height
{ 
public: 
void setHeight(int height);
void setId(int id);
void setName(std::string name);
int getHeight();
int getId();
std::string getName();
private:
int height;
int id;
std::string name;
}; 

Table内部,高度构件的类型为Height

我认为这在实践中会起作用,但是我有另一个问题。在显示此数据时,如果height是直线值(类型StraightHeight(,它应该只显示 intheight。如果是PredefinedHeight,它也应该显示idname。我可以在两个类中std::string getData()一个虚拟方法,它返回必要的数据,但依靠类来格式化字符串等感觉很混乱。

或者,我可以使用动态转换,但担心在这种情况下这可能是不好的做法。

或者为了简单起见,不打扰继承,只有一个类用于Height.如果是直接值,则只需将其他成员留空即可。 有人有更好的建议吗?

注意:这是一个更新的、重写的答案。以前的版本在编辑历史记录中可用

让我们从数据建模的角度来看:Table有一个属性height。该财产可以归Table所有,也可以来自其他来源。人们能想到的最简单方法是使用指向 const 对象的指针。指针应该指向一个 const,因为高度可能是一个预设,并且这些不应该通过Table对象进行更改。

class Table {
int m_myHeight;
const int *m_height = &m_myHeight;
public:
int height() const { return *m_height; }
void setHeight(int newHeight) {
m_height = &m_myHeight;
m_myHeight = newHeight;
}
void setPresetHeight(const int &preset)
{
m_height = &preset;
/* this line is optional */ m_myHeight = *m_height;
}
};
class PresetHeights {
std::vector<int> m_data;
public:
const int &getPreset(int index);
};

这将正常工作,但您可能希望将一些其他属性分配给预设 - Table 对象的"嵌入"高度所没有的属性。例如,预设可以有名称等。

这可以通过保留对"仅"高度或高度预设的引用来完成。由于高度预设的标识符用于为预设表中的标识符编制索引,因此将 id 值设为私有并且只能通过预设表访问可能是有意义的。这为预设表提供了 id 的权力,并在如何实现表方面提供了一些自由度。

下面的例子是用 C++17 编写的,可以在 godbolt 上试用。

#include <string>
#include <variant>
struct Height {
int value;
Height(int value) : value(value) {}
operator int() const { return value; }
};
struct HeightPreset : Height {
using Id = int;
private:
Id id; // the unique identifier of this preset
friend class HeightPresets;
friend int main(); // test harness
public:
std::string name; // name of this preset
template <typename Name>
HeightPreset(Id id, int value, Name &&name) : 
Height(value), id(id), name(std::forward<Name>(name)) {}
};

Table使用std::variant来保存没有高度值(std::monostate(,或HeightHeightPreset

#include <functional>
class Table {
using preset_t = std::reference_wrapper<const HeightPreset>;
std::variant<std::monostate, Height, preset_t> m_height;
public:
std::optional<Height> height() const {
if (auto *customHeight = std::get_if<Height>(&m_height))
return *customHeight;
else if (auto *presetHeight = std::get_if<preset_t>(&m_height))
return std::get<preset_t>(m_height).get();
else
return {};
}
void setHeight(Height newHeight)
{ m_height = newHeight; }
void setHeightPreset(const HeightPreset &preset) 
{ m_height = std::cref(preset); }
bool hasPresetHeight() const { return m_height.index() == 2; }
const HeightPreset &presetHeight() const
{ return std::get<preset_t>(m_height).get(); }
};

预设可按类型HeightPreset的值迭代

预设是从HeightPreset::IdHeightPreset的地图。但首先,我们需要一个迭代器适配器来让我们迭代预设值 - 隐藏我们使用的映射的实现细节std::pair,而不是HeightPreset

#include <map>
template <class K, class V, class C, class A>
class map_cvalue_iterator
{
typename std::map<K, V, C, A>::const_iterator it;
public:
map_cvalue_iterator(typename std::map<K,V>::const_iterator it) : it(it) {}
map_cvalue_iterator(const map_cvalue_iterator &o) : it(o.it) {}
auto &operator=(const map_cvalue_iterator &o) { it = o.it; return *this; }
auto operator++(int) { auto val = *this; ++it; return val; } 
auto &operator++() { ++it; return *this; }
auto operator--(int) { auto val = *this; --it; return val; } 
auto &operator--() { --it; return *this; }
const V& operator*() const { return it->second; }
const V* operator->() const { return it->second; }
bool operator==(map_cvalue_iterator o) const { return it == o.it; }
bool operator!=(map_cvalue_iterator o) const { return it != o.it; }
};
template <class M>
using map_cvalue_iterator_type 
= map_cvalue_iterator<typename M::key_type, typename M::mapped_type,
typename M::key_compare, typename M::allocator_type>;

预设是围绕std::map的薄包装器:

class HeightPresets {
public:
using Id = HeightPreset::Id;
HeightPresets(std::initializer_list<HeightPreset> presets)
{
for (auto &preset : presets)
m_presets.insert({preset.id, preset});
}
auto &get(Id id) const { return m_presets.at(id); }
Id getIdFor(const HeightPreset &preset) const 
{ return preset.id; }
auto begin() const { return  map_cvalue_iterator_type<map_t>(m_presets.cbegin()); }
auto end() const { return map_cvalue_iterator_type<map_t>(m_presets.cend()); }
private:
using map_t = std::map<Id, HeightPreset>;
map_t m_presets;
};

演示如何使用这些类型的简单测试工具:

#include <cassert>
int main() {
const HeightPresets presets{
{1, 5, "A Fiver"},
{2, 10, "A Tenner"}
};
Table aTable;
assert(!aTable.height());
// The table has no height by default
aTable.setHeight(10);
assert(!aTable.hasPresetHeight());
// The height was not a preset
assert(aTable.height() == 10);
// The height was retained

for (auto &preset : presets)
{
aTable.setHeightPreset(preset);
assert(aTable.hasPresetHeight());
// The height was preset
assert(aTable.height() == preset);
// The height has the expected preset's value
assert(presets.getIdFor(aTable.presetHeight()) == preset.id);
// The height has the expected preset's identifier
assert(aTable.presetHeight().name == preset.name);
}
}

预设可按std::pair<HeightPreset::Id, HeightPreset>迭代

如果您不希望使用迭代器适配器,可以将其删除。见下文,并在Godbolt上尝试一下。

#include <map>
class HeightPresets {
public:
using Id = HeightPreset::Id;
HeightPresets(std::initializer_list<HeightPreset> presets)
{
for (auto &preset : presets)
m_presets.insert({preset.id, preset});
}
auto &get(Id id) const { return m_presets.at(id); }
Id getIdFor(const HeightPreset &preset) const 
{ return preset.id; }
auto begin() const { return  m_presets.cbegin(); }
auto end() const { return m_presets.cend(); }
private:
using map_t = std::map<Id, HeightPreset>;
map_t m_presets;
};

和测试工具:

#include <cassert>
int main() {
const HeightPresets presets{
{1, 5, "A Fiver"},
{2, 10, "A Tenner"}
};
Table aTable;
assert(!aTable.height());
// The table has no height by default
aTable.setHeight(10);
assert(!aTable.hasPresetHeight());
// The height was not a preset
assert(aTable.height() == 10);
// The height was retained

for (auto &presetPair : presets)
{
auto &preset = presetPair.second;
aTable.setHeightPreset(preset);
assert(aTable.hasPresetHeight());
// The height was preset
assert(aTable.height() == preset);
// The height has the expected preset's value
assert(presets.getIdFor(aTable.presetHeight()) == preset.id);
// The height has the expected preset's identifier
assert(aTable.presetHeight().name == preset.name);
}
}

height 的值应为 int 或指向具有附加数据的结构

这称为总和类型,又称标记并集

C++有一个标准模板:标准<variant>标头中的 std::variant 。您可能想在里面使用一些智能指针。

如果您不能使用该标头,请使用class中的一些union重新实现它(受此启发,另一个成员区分该联合(,但不要忘记遵循五法则。

我建议阅读更多关于C++的信息。首先,编程 - 原理和实践使用C++和后来的C++11标准n3337。