我需要用不同的const成员数据实现很多派生类。数据处理应在基类中进行,但我找不到访问派生数据的优雅方法。下面的代码可以正常工作,但是我真的不喜欢它。
代码需要在小型嵌入式环境中运行,因此无法大量使用堆或像Boost这样的精美库。
class Base
{
public:
struct SomeInfo
{
const char *name;
const f32_t value;
};
void iterateInfo()
{
// I would love to just write
// for(const auto& info : c_myInfo) {...}
u8_t len = 0;
const auto *returnedInfo = getDerivedInfo(len);
for (int i = 0; i < len; i++)
{
DPRINTF("Name: %s - Value: %f \n", returnedInfo[i].name, returnedInfo[i].value);
}
}
virtual const SomeInfo* getDerivedInfo(u8_t &length) = 0;
};
class DerivedA : public Base
{
public:
const SomeInfo c_myInfo[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
virtual const SomeInfo* getDerivedInfo(u8_t &length) override
{
// Duplicated code in every derived implementation....
length = sizeof(c_myInfo) / sizeof(c_myInfo[0]);
return c_myInfo;
}
};
class DerivedB : public Base
{
public:
const SomeInfo c_myInfo[3] { {"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f} };
virtual const SomeInfo *getDerivedInfo(u8_t &length) override
{
// Duplicated code in every derived implementation....
length = sizeof(c_myInfo) / sizeof(c_myInfo[0]);
return c_myInfo;
}
};
DerivedA instanceA;
DerivedB instanceB;
instanceA.iterateInfo();
instanceB.iterateInfo();
答案 0 :(得分:35)
您在这里不需要任何虚拟或模板。只需向SomeInfo*
添加一个Base
指针及其长度,并提供一个受保护的构造函数即可对其进行初始化(并且由于没有默认的构造函数,因此不会忘记初始化它们)。>
受保护的构造函数不是硬性要求,但是由于Base
不再是抽象基类,因此使受保护的构造函数可以防止Base
实例化。
class Base
{
public:
struct SomeInfo
{
const char *name;
const f32_t value;
};
void iterateInfo()
{
for (int i = 0; i < c_info_len; ++i) {
DPRINTF("Name: %s - Value: %f \n", c_info[i].name,
c_info[i].value);
}
}
protected:
explicit Base(const SomeInfo* info, int len) noexcept
: c_info(info)
, c_info_len(len)
{ }
private:
const SomeInfo* c_info;
int c_info_len;
};
class DerivedA : public Base
{
public:
DerivedA() noexcept
: Base(c_myInfo, sizeof(c_myInfo) / sizeof(c_myInfo[0]))
{ }
private:
const SomeInfo c_myInfo[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
};
class DerivedB : public Base
{
public:
DerivedB() noexcept
: Base(c_myInfo, sizeof(c_myInfo) / sizeof(c_myInfo[0]))
{ }
private:
const SomeInfo c_myInfo[3] {
{"NameB1", 2.1f},
{"NameB2", 2.2f},
{"NameB2", 2.3f}
};
};
您当然可以使用小的零开销包装器/适配器类来代替c_info
和c_info_len
成员,以便提供更好,更安全的访问权限(例如begin()
和{ {1}}支持),但这不在此答案的范围内。
正如彼得·科德斯(Peter Cordes)所指出的那样,这种方法的一个问题是,如果最终代码仍使用虚函数(您所拥有的虚函数),则派生对象现在要增大指针大小加上end()
的大小。 (在您的帖子中未显示。)如果不再有虚拟对象,则对象大小只会增加int
。您确实说过您是在一个小型嵌入式环境中,所以如果其中许多对象同时处于活动状态,则可能需要担心。
Peter还指出,由于您的int
数组是c_myInfo
并且使用常量初始化程序,因此您最好将它们设为const
。这样可以将每个派生对象的大小减小数组的大小。
答案 1 :(得分:13)
您可以将Base
用作模板,并获取const数组的长度。像这样:
template<std::size_t Length>
class Base
{
public:
struct SomeInfo
{
const char *name;
const float value;
};
const SomeInfo c_myInfo[Length];
void iterateInfo()
{
//I would love to just write
for(const auto& info : c_myInfo) {
// work with info
}
}
};
然后从每个基类中相应地初始化数组:
class DerivedA : public Base<2>
{
public:
DerivedA() : Base<2>{ SomeInfo{"NameA1", 1.1f}, {"NameA2", 1.2f} } {}
};
class DerivedB : public Base<3>
{
public:
DerivedB() : Base<3>{ SomeInfo{"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f} } {}
};
然后像往常一样使用。此方法删除了多态性,并且不使用堆分配(例如,没有std::vector
),就像用户 SirNobbyNobbs 所请求的一样。
答案 2 :(得分:8)
好吧,让我们简化所有不必要的并发症:)
您的代码实际上可以归结为以下内容:
SomeInfo.h
struct SomeInfo
{
const char *name;
const f32_t value;
};
void processData(const SomeInfo* c_myInfo, u8_t len);
SomeInfo.cpp
#include "SomeInfo.h"
void processData(const SomeInfo* c_myInfo, u8_t len)
{
for (u8_t i = 0; i < len; i++)
{
DPRINTF("Name: %s - Value: %f \n", c_myInfo[i].name, c_myInfo[i].value);
}
}
data.h
#include "SomeInfo.h"
struct A
{
const SomeInfo info[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
static const u8_t len = 2;
};
struct B
{
const SomeInfo info[3] { {"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f} };
static const u8_t len = 3;
};
main.cpp
#include "data.h"
int
main()
{
A a;
B b;
processData(a.info, A::len);
processData(b.info, B::len);
}
答案 3 :(得分:7)
您可以使用CRTP:
template<class Derived>
class impl_getDerivedInfo
:public Base
{
virtual const SomeInfo *getDerivedInfo(u8_t &length) override
{
//Duplicated code in every derived implementation....
auto& self = static_cast<Derived&>(*this);
length = sizeof(self.c_myInfo) / sizeof(self.c_myInfo[0]);
return self.c_myInfo;
}
};
class DerivedA : public impl_getDerivedInfo<DerivedA>
{
public:
const SomeInfo c_myInfo[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
};
class DerivedB : public impl_getDerivedInfo<DerivedB>
{
public:
const SomeInfo c_myInfo[3] { {"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f} };
};
答案 4 :(得分:6)
以词汇类型开头:
template<class T>
struct span {
T* b = nullptr;
T* e = nullptr;
// these all do something reasonable:
span()=default;
span(span const&)=default;
span& operator=(span const&)=default;
// pair of pointers, or pointer and length:
span( T* s, T* f ):b(s), e(f) {}
span( T* s, size_t l ):span(s, s+l) {}
// construct from an array of known length:
template<size_t N>
span( T(&arr)[N] ):span(arr, N) {}
// Pointers are iterators:
T* begin() const { return b; }
T* end() const { return e; }
// extended container-like utility functions:
T* data() const { return begin(); }
size_t size() const { return end()-begin(); }
bool empty() const { return size()==0; }
T& front() const { return *begin(); }
T& back() const { return *(end()-1); }
};
// This is just here for the other array ctor,
// a span of const int can be constructed from
// an array of non-const int.
template<class T>
struct span<T const> {
T const* b = nullptr;
T const* e = nullptr;
span( T const* s, T const* f ):b(s), e(f) {}
span( T const* s, size_t l ):span(s, s+l) {}
template<size_t N>
span( T const(&arr)[N] ):span(arr, N) {}
template<size_t N>
span( T(&arr)[N] ):span(arr, N) {}
T const* begin() const { return b; }
T const* end() const { return e; }
size_t size() const { return end()-begin(); }
bool empty() const { return size()==0; }
T const& front() const { return *begin(); }
T const& back() const { return *(end()-1); }
};
此类型已通过GSL引入C ++ std
(略有不同)。如果您还没有上述基本词汇类型,就足够了。
跨度代表已知长度的连续对象块的“指针”。
现在我们可以谈谈span<char>
:
class Base
{
public:
void iterateInfo()
{
for(const auto& info : c_mySpan) {
DPRINTF("Name: %s - Value: %f \n", info.name, info.value);
}
}
private:
span<const char> c_mySpan;
Base( span<const char> s ):c_mySpan(s) {}
Base(Base const&)=delete; // probably unsafe
};
现在,您的派生外观如下:
class DerivedA : public Base
{
public:
const SomeInfo c_myInfo[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
DerivedA() : Base(c_myInfo) {}
};
每个Base
有两个指针的开销。一个vtable使用一个指针,使您的类型抽象,添加间接寻址,并为每个Derived
类型添加一个全局vtable。
现在,从理论上讲,您可以将此开销降低到数组的长度,并假定数组数据在Base
之后立即开始,但这是脆弱的,不可移植的,并且仅当绝望。
尽管您可能对嵌入式代码中的模板不屑一顾(因为您应该进行任何类型的代码生成;代码生成意味着您可以从O(1)代码生成更多的O(1)二进制文件)。跨度词汇类型很紧凑,如果您的编译器设置相当激进,则应该内联为空。
答案 5 :(得分:5)
CRTP + std :: array怎么样?无需额外的变量,v-ptr或虚拟函数调用。 std :: array是围绕C样式数组的非常薄的包装器。空基类优化可确保不浪费任何空间。在我看来,它“足够”优雅:)
template<typename Derived>
class BaseT
{
public:
struct SomeInfo
{
const char *name;
const f32_t value;
};
void iterateInfo()
{
Derived* pDerived = static_cast<Derived*>(this);
for (const auto& i: pDerived->c_myInfo)
{
printf("Name: %s - Value: %f \n", i.name, i.value);
}
}
};
class DerivedA : public BaseT<DerivedA>
{
public:
const std::array<SomeInfo,2> c_myInfo { { {"NameA1", 1.1f}, {"NameA2", 1.2f} } };
};
class DerivedB : public BaseT<DerivedB>
{
public:
const std::array<SomeInfo, 3> c_myInfo { { {"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f} } };
};
答案 6 :(得分:4)
因此,如果您真的想按自己的方式组织数据,那么我就会明白为什么您会在现实生活中这么做:
使用C ++ 17的一种方法是返回代表内容列表的“视图”对象。然后可以在C ++ 11 for
语句中使用它。您可以编写将start+len
转换为视图的基本函数,因此无需将其添加到虚拟方法对象中。
创建与C ++ 11兼容的语句的视图对象并不难。另外,您可以考虑使用C ++ 98 for_each模板,这些模板可以采用开始和结束迭代器:您的开始迭代器为start
;最终迭代器为start+len
。
答案 7 :(得分:3)
您可以将数据移动到类之外的二维数组中,并让每个类返回包含相关数据的索引。
struct SomeInfo
{
const char *name;
const f32_t value;
};
const vector<vector<SomeInfo>> masterStore{
{{"NameA1", 1.1f}, {"NameA2", 1.2f}},
{{"NameB1", 2.1f}, {"NameB2", 2.2f}, {"NameB2", 2.3f}}
};
class Base
{
public:
void iterateInfo()
{
// I would love to just write
// for(const auto& info : c_myInfo) {...}
u8_t len = 0;
auto index(getIndex());
for(const auto& data : masterStore[index])
{
DPRINTF("Name: %s - Value: %f \n", data.name, data.value);
}
}
virtual int getIndex() = 0;
};
class DerivedA : public Base
{
public:
int getIndex() override
{
return 0;
}
};
class DerivedB : public Base
{
public:
int getIndex() override
{
return 1;
}
};
DerivedA instanceA;
DerivedB instanceB;
instanceA.iterateInfo();
instanceB.iterateInfo();
答案 8 :(得分:3)
仅使虚函数直接返回对数据的引用(您需要更改为向量-否则不能使用大小不同的数组或C样式数组类型):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
或如果指针是唯一可行的选择,则将其作为指针范围(如果可能,最好使用迭代器/范围适配器-对此有更多说明):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
要使这最后一种方法与基于基于范围的for循环一起使用:一种方法是制作一个具有功能begin()/end()
的小型“范围视图”类型-与{ {1}}
begin()/end()
然后用以下代码构造它:
template<class T>
struct ptr_range {
std::pair<T*, T*> range_;
auto begin(){return range_.first;}
auto end(){return range_.second;}
};
如果不需要模板,很容易将其设置为非模板。