さまざまな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();
ここでは、仮想またはテンプレートは必要ありません。 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()
やend()
サポートなど)。この回答の範囲外。
Peter Cordesが指摘したように、このアプローチの1つの問題は、最終的なコードで仮想(まだ使用していない仮想関数)を使用している場合、派生オブジェクトがポインターのサイズとint
のサイズだけ大きくなることです。投稿に示されています。)バーチャルがもうない場合、オブジェクトのサイズはint
だけ増加します。あなたは小さな組み込み環境を使用していると言っていたので、これらのオブジェクトの多くが同時に存続する場合、これは心配する必要があるかもしれません。
Peterはまた、あなたのc_myInfo
配列はconst
and定数初期化子を使用しているため、それらをstatic
にすることもできると指摘しました。これにより、各派生オブジェクトのサイズが配列のサイズだけ小さくなります。
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が要求されました。
さて、すべての不要な複雑化を簡素化しましょう:)
あなたのコードは実際には次のように要約されます:
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);
}
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} };
};
語彙タイプから始めます。
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
ごとに2つのポインターのオーバーヘッドがあります。 vtableは1つのポインターを使用し、型を抽象化し、間接指定を追加し、Derived
型ごとに1つのグローバル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} } };
};
それで、あなたが本当にあなたのデータをそれがそうであるように整理したいなら、そして私があなたが実際の生活でそうする理由を見ることができます:
C++ 17の1つの方法は、コンテンツリストを表す「ビュー」オブジェクトを返すことです。これは、C++ 11 for
ステートメントで使用できます。 start+len
をビューに変換する基本関数を記述できるため、仮想メソッドcruftに追加する必要はありません。
C++ 11 forステートメントと互換性のあるビューオブジェクトを作成することはそれほど難しくありません。または、開始イテレータと終了イテレータを取ることができるC++ 98 for_eachテンプレートの使用を検討することもできます。開始イテレータはstart
;です。終了イテレータはstart+len
です。
データをクラス外の2次元配列に移動し、各クラスに関連データを含むインデックスを返させることができます。
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();
仮想関数がデータへの参照を直接返すようにするだけです(その後、ベクトルに変更する必要があります-異なるサイズの配列またはCスタイルの配列型では不可能です)。
_virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
_
またはポインターが唯一の実行可能なオプションである場合、ポインター範囲として(可能な場合はイテレーター/範囲アダプターが推奨されます-さらに詳しく):
_virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
_
この最後のメソッドを範囲ベースのforループで機能させるには、1つの方法は、関数begin()/end()
を持つ小さな「範囲ビュー」タイプを作成することです-begin()/end()
_template<class T>
struct ptr_range {
std::pair<T*, T*> range_;
auto begin(){return range_.first;}
auto end(){return range_.second;}
};
_
次に、次のように構成します:
_virtual ptr_range<SomeInfo> getDerivedInfo() override
{
return {std::begin(c_myInfo), std::end(c_myInfo)};
}
_
テンプレートが不要な場合は、非テンプレートにするのは簡単です。