Sir*_*bbs 34 c++ inheritance c++11
I need to implement a lot of derived classes with different const member data. The data processing should be handled in the base class, but I can't find an elegant way to access the derived data. The code below is working, but I really don't like it.
The code needs to run in a small embedded environment so extensive usage of the heap or fancy libraries like Boost is no option.
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();
Run Code Online (Sandbox Code Playgroud)
Nik*_* C. 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}
};
};
Run Code Online (Sandbox Code Playgroud)
您当然可以使用小的零开销包装器/适配器类来代替c_infoand c_info_len成员,以提供更好,更安全的访问(如begin()和end()支持),但这不在此答案的范围内。
正如Peter Cordes指出的那样,这种方法的一个问题是,int如果最终代码仍使用虚函数(您的帖子中未显示的虚函数),则派生的对象现在要比指针的大小加上an的大小大。如果不再有虚拟物体,则对象大小只会增加一个int。您确实说过您在一个小型嵌入式环境中,所以如果其中许多对象同时处于活动状态,则可能需要担心。
彼得还指出,由于您的c_myInfo数组是const 并且使用常量初始化器,因此您最好将它们设为static。这将通过数组的大小减少每个派生对象的大小。
Dei*_*Dei 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
}
}
};
Run Code Online (Sandbox Code Playgroud)
然后从每个基类中相应地初始化数组:
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} } {}
};
Run Code Online (Sandbox Code Playgroud)
然后照常使用。此方法删除了多态性,并且不使用堆分配(例如no std::vector),就像用户SirNobbyNobbs所请求的那样。
好吧,让我们简化所有不必要的复杂性:)
您的代码实际上可以归结为以下内容:
SomeInfo.h
struct SomeInfo
{
const char *name;
const f32_t value;
};
void processData(const SomeInfo* c_myInfo, u8_t len);
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
数据
#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;
};
Run Code Online (Sandbox Code Playgroud)
main.cpp
#include "data.h"
int
main()
{
A a;
B b;
processData(a.info, A::len);
processData(b.info, B::len);
}
Run Code Online (Sandbox Code Playgroud)
您可以使用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} };
};
Run Code Online (Sandbox Code Playgroud)
从词汇类型开始:
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); }
};
Run Code Online (Sandbox Code Playgroud)
该类型已std通过GSL 引入到C ++中(略有不同)。如果您还没有上述基本词汇类型,就足够了。
跨度代表已知长度的连续对象块的“指针”。
现在我们可以谈谈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
};
Run Code Online (Sandbox Code Playgroud)
现在,您的派生看起来像:
class DerivedA : public Base
{
public:
const SomeInfo c_myInfo[2] { {"NameA1", 1.1f}, {"NameA2", 1.2f} };
DerivedA() : Base(c_myInfo) {}
};
Run Code Online (Sandbox Code Playgroud)
每个有两个指针的开销Base。一个vtable使用一个指针,使您的类型抽象,添加间接寻址,并为每个Derived类型添加一个全局vtable 。
现在,从理论上讲,您现在可以将开销降到数组的长度,并假定数组数据从之后开始Base,但这是脆弱的,不可移植的,并且仅在绝望时才有用。
尽管您可能对嵌入式代码中的模板不屑一顾(因为您应该进行任何类型的代码生成;但是代码生成意味着您可以从O(1)代码生成更多的O(1)二进制文件)。跨度词汇类型很紧凑,如果您的编译器设置相当激进,则应该内联为空。
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} } };
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6499 次 |
| 最近记录: |