How do I remove this inheritance-related code smell?How do you set, clear, and toggle a single bit?Prefer composition over inheritance?How do I iterate over the words of a string?How can I profile C++ code running on Linux?Difference between private, public, and protected inheritancePython class inherits objectC++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?What are the nuances of scope prototypal / prototypical inheritance in AngularJS?Why not inherit from List<T>?Downcasting best-practice (C++)
What is the best delay to use between characters sent to the serial port
How can I check type T is among parameter pack Ts... in C++?
Why is a blank required between "[[" and "-e xxx" in ksh?
MH370 blackbox - is it still possible to retrieve data from it?
Why cruise at 7000' in an A319?
Pronunciation of "œuf" in "deux œufs kinder" and "bœuf "in "deux bœufs bourguignons" as an exception to silent /f/ in the plural
Signing using digital signatures?
Intuitively, why does putting capacitors in series decrease the equivalent capacitance?
“Transitive verb” + interrupter+ “object”?
What's the point of DHS warning passengers about Manila airport?
Averting Real Women Don’t Wear Dresses
Can you get infinite turns with this 2 card combo?
Should I include salary information on my CV?
Is there any set of 2-6 notes that doesn't have a chord name?
A player is constantly pestering me about rules, what do I do as a DM?
Would adding an external lens allow one area outside the focal plane to be in focus?
Should I tell my insurance company I have an unsecured loan for my new car?
Forgot chonantanu after already making havdalah over wine
If a high rpm motor is run at lower rpm, will it produce more torque?
Three column layout
Did Chinese school textbook maps (c. 1951) "depict China as stretching even into the central Asian republics"?
One folder two different locations on ubuntu 18.04
Short story with brother-sister conjoined twins as protagonists?
How hard is it to sell a home which is currently mortgaged?
How do I remove this inheritance-related code smell?
How do you set, clear, and toggle a single bit?Prefer composition over inheritance?How do I iterate over the words of a string?How can I profile C++ code running on Linux?Difference between private, public, and protected inheritancePython class inherits objectC++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming?What are the nuances of scope prototypal / prototypical inheritance in AngularJS?Why not inherit from List<T>?Downcasting best-practice (C++)
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;
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();
c++ c++11 inheritance
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
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();
c++ c++11 inheritance
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
4
IfSomeInfo c_myInfo[3]isconstand has a compile-time constant initializer, why do you have it inside the object instead ofstatic? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job forenum..)
– Peter Cordes
Jun 17 at 20:54
2
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49
add a comment |
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();
c++ c++11 inheritance
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
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();
c++ c++11 inheritance
c++ c++11 inheritance
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
edited Jun 19 at 12:35
Masoud Darvishian
1,3554 gold badges18 silver badges30 bronze badges
1,3554 gold badges18 silver badges30 bronze badges
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
asked Jun 17 at 8:23
SirNobbyNobbsSirNobbyNobbs
1362 silver badges7 bronze badges
1362 silver badges7 bronze badges
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
SirNobbyNobbs is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
4
IfSomeInfo c_myInfo[3]isconstand has a compile-time constant initializer, why do you have it inside the object instead ofstatic? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job forenum..)
– Peter Cordes
Jun 17 at 20:54
2
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49
add a comment |
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
4
IfSomeInfo c_myInfo[3]isconstand has a compile-time constant initializer, why do you have it inside the object instead ofstatic? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job forenum..)
– Peter Cordes
Jun 17 at 20:54
2
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
4
4
If
SomeInfo c_myInfo[3] is const and has a compile-time constant initializer, why do you have it inside the object instead of static? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job for enum..)– Peter Cordes
Jun 17 at 20:54
If
SomeInfo c_myInfo[3] is const and has a compile-time constant initializer, why do you have it inside the object instead of static? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job for enum..)– Peter Cordes
Jun 17 at 20:54
2
2
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49
add a comment |
9 Answers
9
active
oldest
votes
You don't need any virtuals or templates here. Just add a SomeInfo* pointer and its length to Base, and provide a protected constructor to initialize them (and since there's no default constructor, it won't be possible to forget to initialize them).
The constructor being protected is not a hard requirement, but since Base is not an abstract base class anymore, making the constructor protected prevents Base from being instantiated.
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
;
;
You can of course use a small, zero-overhead wrapper/adapter class instead of the c_info and c_info_len members in order to provide nicer and safer access (like begin() and end() support), but that's outside the scope of this answer.
As Peter Cordes pointed out, one issue with this approach is that the derived objects are now larger by the size of a pointer plus the size of an int if your final code still uses virtuals (virtual functions you haven't showed in your post.) If there's no virtuals anymore, then object size is only going to increase by an int. You did say that you're on a small embedded environment, so if a lot of these objects are going to be alive at the same time, then this might be something to worry about.
Peter also pointed out that since your c_myInfo arrays are const and use constant initializers, you might as well make them static. This will reduce the size of each derived object by the size of the array.
1
This grows the size of every object by 1 pointer + 1int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)
– Peter Cordes
Jun 17 at 20:46
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
|
show 5 more comments
You could make Base a template and take the length of your const array. Something like this:
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
;
And then initialize the array accordingly from each base class:
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
;
And then use as you normally would. This method removes the polymorphism and uses no heap allocation (e.g. no std::vector), just as user SirNobbyNobbs requested.
1
To save code size, makeiterateInfoa non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that doiterateInfo(c_myInfo, Length);So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.
– Peter Cordes
Jun 18 at 5:56
add a comment |
Okay then let's simplify all the unnecessary complications :)
Your code really boils down to the following:
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);
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
There's no point having au8_t len = 3;member in each struct; the length of theinfo[]array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each ofAandBthat passes the right args to a commonprocessDatafunction. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (finalon the derived type functions allows that in more cases.)
– Peter Cordes
Jun 17 at 21:08
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make itstatic const int len;Theu8will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assumingalignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of theiralignof(), so in an array all the elements have correct alignment.
– Peter Cordes
Jun 17 at 21:51
|
show 2 more comments
You can use 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 ;
;
DoesgetDerivedInfostill need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.
– Peter Cordes
Jun 18 at 0:41
1
@PeterCordes I supposegetDerivedInfomust be virtual because this is the assumption done in the question (Objects are accessed though expression of typeBase). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
add a comment |
Start with a vocabulary type:
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);
;
this type has been introduced to C++ std (with slight differences) via the GSL. The basic vocabulary type above is enough if you don't already have it.
A span represents a "pointer" to a block of contiguous objects of known length.
Now we can talk about a 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
;
Now your derived looks like:
class DerivedA : public Base
public:
const SomeInfo c_myInfo[2] "NameA1", 1.1f, "NameA2", 1.2f ;
DerivedA() : Base(c_myInfo)
;
This has overhead of two pointers per Base. A vtable uses one pointer, makes your type abstract, adds indirection, and adds one global vtable per Derived type.
Now, in theory, you could get the overhead of this down to the length of the array, and presume that the array data starts right after Base, but that is fragile, non-portable and only useful if desperate.
While you may be rightly leery of templates in embedded code (as you should be of any kind of code generation; code generation means you can generate more than O(1) binary from O(1) code). The span vocabulary type is compact and should be inlined to nothing if your compiler settings are reasonably aggressive.
add a comment |
So if you really want to keep your data organised the way it is, and I can see why you would in real life:
One way with C++17 would be to return a "view" object representing your content list. This can then be used in a C++11 for statement. You could write a base function that converts start+len into a view, so you don't need to add to the virtual method cruft.
It is not that difficult to create a view object that is compatible with C++11 for statement. Alternatively, you could consider using the C++98 for_each templates that can take a begin and end iterator: Your start iterator is start; the end iterator is start+len.
add a comment |
How about CRTP + std::array? No extra variables, v-ptr or virtual function calls. std::array is a very thin wrapper around C style array. Empty base class optimization ensures no space is wasted. It looks "elegant" enough to me :)
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 ;
;
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
You can move your data into a two-dimensional array outside of the classes and have each class return an index which contains relevant data.
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();
3
Why aconst std::vectorfor compile-time-constant data with known fixed size? Seems like a job for a flat 1Dstd::array<SomeInfo>with each derived class knowing the right start index + offset. (GetIndex returns astd::pair<int,int>). Or astd::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array ofSomeInfoobjects withnullptrterminators for the end of each sub-array, if you only ever want to iterate in order.
– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point usingvector<vector<T>>overarray<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.
– Peter Cordes
Jun 18 at 15:49
add a comment |
Just make the virtual function return a reference to the data directly (you need to change to vector then - not possible with array or C style array types with different sizes):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
or if pointers are the only feasible option, as a pointer range (iterators/range adapter would be preferred though if possible - more on that):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
To make this last method work with range-based for loop: one way is to make a small 'range view' type that has the functions begin()/end() - essential a pair with begin()/end()
Example:
template<class T>
struct ptr_range
std::pair<T*, T*> range_;
auto begin()return range_.begin();
auto end()return range_.end();
;
Then construct it with:
virtual ptr_range<SomeInfo> getDerivedInfo() override
return std::begin(c_myInfo), std::end(c_myInfo);
It is easy to make it non-template if a template is not desired.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
SirNobbyNobbs is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56627438%2fhow-do-i-remove-this-inheritance-related-code-smell%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
9 Answers
9
active
oldest
votes
9 Answers
9
active
oldest
votes
active
oldest
votes
active
oldest
votes
You don't need any virtuals or templates here. Just add a SomeInfo* pointer and its length to Base, and provide a protected constructor to initialize them (and since there's no default constructor, it won't be possible to forget to initialize them).
The constructor being protected is not a hard requirement, but since Base is not an abstract base class anymore, making the constructor protected prevents Base from being instantiated.
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
;
;
You can of course use a small, zero-overhead wrapper/adapter class instead of the c_info and c_info_len members in order to provide nicer and safer access (like begin() and end() support), but that's outside the scope of this answer.
As Peter Cordes pointed out, one issue with this approach is that the derived objects are now larger by the size of a pointer plus the size of an int if your final code still uses virtuals (virtual functions you haven't showed in your post.) If there's no virtuals anymore, then object size is only going to increase by an int. You did say that you're on a small embedded environment, so if a lot of these objects are going to be alive at the same time, then this might be something to worry about.
Peter also pointed out that since your c_myInfo arrays are const and use constant initializers, you might as well make them static. This will reduce the size of each derived object by the size of the array.
1
This grows the size of every object by 1 pointer + 1int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)
– Peter Cordes
Jun 17 at 20:46
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
|
show 5 more comments
You don't need any virtuals or templates here. Just add a SomeInfo* pointer and its length to Base, and provide a protected constructor to initialize them (and since there's no default constructor, it won't be possible to forget to initialize them).
The constructor being protected is not a hard requirement, but since Base is not an abstract base class anymore, making the constructor protected prevents Base from being instantiated.
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
;
;
You can of course use a small, zero-overhead wrapper/adapter class instead of the c_info and c_info_len members in order to provide nicer and safer access (like begin() and end() support), but that's outside the scope of this answer.
As Peter Cordes pointed out, one issue with this approach is that the derived objects are now larger by the size of a pointer plus the size of an int if your final code still uses virtuals (virtual functions you haven't showed in your post.) If there's no virtuals anymore, then object size is only going to increase by an int. You did say that you're on a small embedded environment, so if a lot of these objects are going to be alive at the same time, then this might be something to worry about.
Peter also pointed out that since your c_myInfo arrays are const and use constant initializers, you might as well make them static. This will reduce the size of each derived object by the size of the array.
1
This grows the size of every object by 1 pointer + 1int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)
– Peter Cordes
Jun 17 at 20:46
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
|
show 5 more comments
You don't need any virtuals or templates here. Just add a SomeInfo* pointer and its length to Base, and provide a protected constructor to initialize them (and since there's no default constructor, it won't be possible to forget to initialize them).
The constructor being protected is not a hard requirement, but since Base is not an abstract base class anymore, making the constructor protected prevents Base from being instantiated.
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
;
;
You can of course use a small, zero-overhead wrapper/adapter class instead of the c_info and c_info_len members in order to provide nicer and safer access (like begin() and end() support), but that's outside the scope of this answer.
As Peter Cordes pointed out, one issue with this approach is that the derived objects are now larger by the size of a pointer plus the size of an int if your final code still uses virtuals (virtual functions you haven't showed in your post.) If there's no virtuals anymore, then object size is only going to increase by an int. You did say that you're on a small embedded environment, so if a lot of these objects are going to be alive at the same time, then this might be something to worry about.
Peter also pointed out that since your c_myInfo arrays are const and use constant initializers, you might as well make them static. This will reduce the size of each derived object by the size of the array.
You don't need any virtuals or templates here. Just add a SomeInfo* pointer and its length to Base, and provide a protected constructor to initialize them (and since there's no default constructor, it won't be possible to forget to initialize them).
The constructor being protected is not a hard requirement, but since Base is not an abstract base class anymore, making the constructor protected prevents Base from being instantiated.
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
;
;
You can of course use a small, zero-overhead wrapper/adapter class instead of the c_info and c_info_len members in order to provide nicer and safer access (like begin() and end() support), but that's outside the scope of this answer.
As Peter Cordes pointed out, one issue with this approach is that the derived objects are now larger by the size of a pointer plus the size of an int if your final code still uses virtuals (virtual functions you haven't showed in your post.) If there's no virtuals anymore, then object size is only going to increase by an int. You did say that you're on a small embedded environment, so if a lot of these objects are going to be alive at the same time, then this might be something to worry about.
Peter also pointed out that since your c_myInfo arrays are const and use constant initializers, you might as well make them static. This will reduce the size of each derived object by the size of the array.
edited Jun 18 at 6:24
answered Jun 17 at 8:51
Nikos C.Nikos C.
39.1k6 gold badges45 silver badges75 bronze badges
39.1k6 gold badges45 silver badges75 bronze badges
1
This grows the size of every object by 1 pointer + 1int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)
– Peter Cordes
Jun 17 at 20:46
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
|
show 5 more comments
1
This grows the size of every object by 1 pointer + 1int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)
– Peter Cordes
Jun 17 at 20:46
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
1
1
This grows the size of every object by 1 pointer + 1
int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)– Peter Cordes
Jun 17 at 20:46
This grows the size of every object by 1 pointer + 1
int. If you have many more instances of these objects than you do derived types, the OP's solution uses less total memory (data + code). They mention they're in a memory-constrained embedded environment. On a high-end system, the extra level of indirection is still a downside. (At least the pointer will typically be in the same cache line as the start of the data and the vtable pointer, so the extra latency is just 1 L1d cache ~load-use latency. Not an extra cache miss)– Peter Cordes
Jun 17 at 20:46
1
1
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@PeterCordes: Note that the OPs solution grows the size of every object by ??? due to the virtual methods. That's probably 1 pointer, but it's worth noting that the size regression is smaller than it first appears. This also has fewer indirections, since we can directly deference the given pointer, wheras the OP's code has to dereference the virtual method, then call that to get a pointer, then dereference the pointer.
– Mooing Duck
Jun 18 at 0:31
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@MooingDuck: (successful) Branch prediction / speculation hides the latency of the function pointer, but yes it's a pretty terrible choice if you don't need polymorphism. It's plausible that polymorphism merely for the purpose of code-reuse is worth it if they're very memory-constrained, but likely having callers that statically know the type pass the right args to generic functions is the way to go, maybe by writing simple non-virtual inline helper functions that pass a pointer+size at each call site, or whatever other way you get a C++ compiler to emit machine code that does that.
– Peter Cordes
Jun 18 at 0:37
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes That's a good point. I updated the answer.
– Nikos C.
Jun 18 at 5:07
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
@PeterCordes: "small embedded environments" usually do not feature performance enhancing logic such as "Branch prediction / speculation" because it would add significantly to the transistor count (affecting cost, size, and power consumption)
– Ben Voigt
Jun 18 at 5:28
|
show 5 more comments
You could make Base a template and take the length of your const array. Something like this:
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
;
And then initialize the array accordingly from each base class:
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
;
And then use as you normally would. This method removes the polymorphism and uses no heap allocation (e.g. no std::vector), just as user SirNobbyNobbs requested.
1
To save code size, makeiterateInfoa non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that doiterateInfo(c_myInfo, Length);So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.
– Peter Cordes
Jun 18 at 5:56
add a comment |
You could make Base a template and take the length of your const array. Something like this:
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
;
And then initialize the array accordingly from each base class:
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
;
And then use as you normally would. This method removes the polymorphism and uses no heap allocation (e.g. no std::vector), just as user SirNobbyNobbs requested.
1
To save code size, makeiterateInfoa non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that doiterateInfo(c_myInfo, Length);So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.
– Peter Cordes
Jun 18 at 5:56
add a comment |
You could make Base a template and take the length of your const array. Something like this:
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
;
And then initialize the array accordingly from each base class:
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
;
And then use as you normally would. This method removes the polymorphism and uses no heap allocation (e.g. no std::vector), just as user SirNobbyNobbs requested.
You could make Base a template and take the length of your const array. Something like this:
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
;
And then initialize the array accordingly from each base class:
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
;
And then use as you normally would. This method removes the polymorphism and uses no heap allocation (e.g. no std::vector), just as user SirNobbyNobbs requested.
edited Jun 17 at 23:58
Peter Mortensen
14.2k19 gold badges88 silver badges114 bronze badges
14.2k19 gold badges88 silver badges114 bronze badges
answered Jun 17 at 8:44
DeiDeiDeiDei
6,5015 gold badges36 silver badges57 bronze badges
6,5015 gold badges36 silver badges57 bronze badges
1
To save code size, makeiterateInfoa non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that doiterateInfo(c_myInfo, Length);So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.
– Peter Cordes
Jun 18 at 5:56
add a comment |
1
To save code size, makeiterateInfoa non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that doiterateInfo(c_myInfo, Length);So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.
– Peter Cordes
Jun 18 at 5:56
1
1
To save code size, make
iterateInfo a non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that do iterateInfo(c_myInfo, Length); So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.– Peter Cordes
Jun 18 at 5:56
To save code size, make
iterateInfo a non-member function that takes a pointer + Length as function args, and give the derived classes simple inline functions that do iterateInfo(c_myInfo, Length); So the loop has one shared implementation and callers just pass it args. Unfortunately I don't think putting it in the base class would get most compilers to only make one definition, unless they use identical code folding optimizations to merge identical function definitions.– Peter Cordes
Jun 18 at 5:56
add a comment |
Okay then let's simplify all the unnecessary complications :)
Your code really boils down to the following:
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);
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
There's no point having au8_t len = 3;member in each struct; the length of theinfo[]array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each ofAandBthat passes the right args to a commonprocessDatafunction. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (finalon the derived type functions allows that in more cases.)
– Peter Cordes
Jun 17 at 21:08
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make itstatic const int len;Theu8will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assumingalignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of theiralignof(), so in an array all the elements have correct alignment.
– Peter Cordes
Jun 17 at 21:51
|
show 2 more comments
Okay then let's simplify all the unnecessary complications :)
Your code really boils down to the following:
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);
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
There's no point having au8_t len = 3;member in each struct; the length of theinfo[]array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each ofAandBthat passes the right args to a commonprocessDatafunction. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (finalon the derived type functions allows that in more cases.)
– Peter Cordes
Jun 17 at 21:08
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make itstatic const int len;Theu8will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assumingalignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of theiralignof(), so in an array all the elements have correct alignment.
– Peter Cordes
Jun 17 at 21:51
|
show 2 more comments
Okay then let's simplify all the unnecessary complications :)
Your code really boils down to the following:
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);
Okay then let's simplify all the unnecessary complications :)
Your code really boils down to the following:
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);
edited Jun 17 at 22:45
answered Jun 17 at 9:34
Adam ZahranAdam Zahran
6177 silver badges23 bronze badges
6177 silver badges23 bronze badges
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
There's no point having au8_t len = 3;member in each struct; the length of theinfo[]array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each ofAandBthat passes the right args to a commonprocessDatafunction. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (finalon the derived type functions allows that in more cases.)
– Peter Cordes
Jun 17 at 21:08
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make itstatic const int len;Theu8will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assumingalignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of theiralignof(), so in an array all the elements have correct alignment.
– Peter Cordes
Jun 17 at 21:51
|
show 2 more comments
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
There's no point having au8_t len = 3;member in each struct; the length of theinfo[]array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each ofAandBthat passes the right args to a commonprocessDatafunction. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (finalon the derived type functions allows that in more cases.)
– Peter Cordes
Jun 17 at 21:08
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make itstatic const int len;Theu8will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assumingalignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of theiralignof(), so in an array all the elements have correct alignment.
– Peter Cordes
Jun 17 at 21:51
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
Given the provided example you are right.But this is just a simplification to highlight my problem. The base class is already several hundred lines of code and every derived class also has much more inside than just the const info.
– SirNobbyNobbs
Jun 17 at 9:38
6
6
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
Well, I can imagine. All I can suggest is to use composition instead of inheritance along with simple functions. Coding can be a simple and pleasurable experience. We just complicate everything for some reason :)
– Adam Zahran
Jun 17 at 9:42
3
3
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
@SirNobbyNobbs "The base class is already several hundred lines of code" - That is more of a code smell than anything that your simplified example might highlight,
– Goyo
Jun 17 at 19:43
3
3
There's no point having a
u8_t len = 3; member in each struct; the length of the info[] array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each of A and B that passes the right args to a common processData function. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (final on the derived type functions allows that in more cases.)– Peter Cordes
Jun 17 at 21:08
There's no point having a
u8_t len = 3; member in each struct; the length of the info[] array member is already statically known as part of the derived type. @SirNobbyNobbs: What you could do is have a small inline wrapper in each of A and B that passes the right args to a common processData function. It can be virtual if you need it to be, but letting it inline into each call site when you have full type info is good. (final on the derived type functions allows that in more cases.)– Peter Cordes
Jun 17 at 21:08
1
1
If you want to require callers to manually inline, instead of writing a wrapper function, then make it
static const int len; The u8 will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assuming alignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of their alignof(), so in an array all the elements have correct alignment.– Peter Cordes
Jun 17 at 21:51
If you want to require callers to manually inline, instead of writing a wrapper function, then make it
static const int len; The u8 will probably end up still costing you 4 bytes, because it's inside a struct that will get 4-byte alignment (assuming alignof(float) = 4. So the struct size will be a multiple of 4 bytes, because compilers always pad structs to a multiple of their alignof(), so in an array all the elements have correct alignment.– Peter Cordes
Jun 17 at 21:51
|
show 2 more comments
You can use 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 ;
;
DoesgetDerivedInfostill need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.
– Peter Cordes
Jun 18 at 0:41
1
@PeterCordes I supposegetDerivedInfomust be virtual because this is the assumption done in the question (Objects are accessed though expression of typeBase). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
add a comment |
You can use 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 ;
;
DoesgetDerivedInfostill need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.
– Peter Cordes
Jun 18 at 0:41
1
@PeterCordes I supposegetDerivedInfomust be virtual because this is the assumption done in the question (Objects are accessed though expression of typeBase). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
add a comment |
You can use 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 ;
;
You can use 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 ;
;
answered Jun 17 at 8:58
OlivOliv
11.1k1 gold badge20 silver badges58 bronze badges
11.1k1 gold badge20 silver badges58 bronze badges
DoesgetDerivedInfostill need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.
– Peter Cordes
Jun 18 at 0:41
1
@PeterCordes I supposegetDerivedInfomust be virtual because this is the assumption done in the question (Objects are accessed though expression of typeBase). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
add a comment |
DoesgetDerivedInfostill need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.
– Peter Cordes
Jun 18 at 0:41
1
@PeterCordes I supposegetDerivedInfomust be virtual because this is the assumption done in the question (Objects are accessed though expression of typeBase). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
Does
getDerivedInfo still need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.– Peter Cordes
Jun 18 at 0:41
Does
getDerivedInfo still need to be virtual here? Also you could turn the duplicated code into just a simple caller for a base-class generic function that takes pointer + length. That would give you clean syntax for a way to get the compiler to pass pointer+length to a common non-duplicated implementation.– Peter Cordes
Jun 18 at 0:41
1
1
@PeterCordes I suppose
getDerivedInfo must be virtual because this is the assumption done in the question (Objects are accessed though expression of type Base). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.– Oliv
Jun 18 at 5:52
@PeterCordes I suppose
getDerivedInfo must be virtual because this is the assumption done in the question (Objects are accessed though expression of type Base). The generic function would be so short that it will be inlined whatsoever, resulting in the same code (the expression that initialize length is a constant expression). Virtualization could be implemented by hand, like in the NikosC. answer, in a memory efficient way, but the compiler will not any more be able to perform devirtualization.– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
@PeterCordes What is laking to the language is a way to express that a specific non-static data member value in a base class is the same for all object that have the same dynamic type.
– Oliv
Jun 18 at 5:52
add a comment |
Start with a vocabulary type:
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);
;
this type has been introduced to C++ std (with slight differences) via the GSL. The basic vocabulary type above is enough if you don't already have it.
A span represents a "pointer" to a block of contiguous objects of known length.
Now we can talk about a 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
;
Now your derived looks like:
class DerivedA : public Base
public:
const SomeInfo c_myInfo[2] "NameA1", 1.1f, "NameA2", 1.2f ;
DerivedA() : Base(c_myInfo)
;
This has overhead of two pointers per Base. A vtable uses one pointer, makes your type abstract, adds indirection, and adds one global vtable per Derived type.
Now, in theory, you could get the overhead of this down to the length of the array, and presume that the array data starts right after Base, but that is fragile, non-portable and only useful if desperate.
While you may be rightly leery of templates in embedded code (as you should be of any kind of code generation; code generation means you can generate more than O(1) binary from O(1) code). The span vocabulary type is compact and should be inlined to nothing if your compiler settings are reasonably aggressive.
add a comment |
Start with a vocabulary type:
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);
;
this type has been introduced to C++ std (with slight differences) via the GSL. The basic vocabulary type above is enough if you don't already have it.
A span represents a "pointer" to a block of contiguous objects of known length.
Now we can talk about a 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
;
Now your derived looks like:
class DerivedA : public Base
public:
const SomeInfo c_myInfo[2] "NameA1", 1.1f, "NameA2", 1.2f ;
DerivedA() : Base(c_myInfo)
;
This has overhead of two pointers per Base. A vtable uses one pointer, makes your type abstract, adds indirection, and adds one global vtable per Derived type.
Now, in theory, you could get the overhead of this down to the length of the array, and presume that the array data starts right after Base, but that is fragile, non-portable and only useful if desperate.
While you may be rightly leery of templates in embedded code (as you should be of any kind of code generation; code generation means you can generate more than O(1) binary from O(1) code). The span vocabulary type is compact and should be inlined to nothing if your compiler settings are reasonably aggressive.
add a comment |
Start with a vocabulary type:
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);
;
this type has been introduced to C++ std (with slight differences) via the GSL. The basic vocabulary type above is enough if you don't already have it.
A span represents a "pointer" to a block of contiguous objects of known length.
Now we can talk about a 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
;
Now your derived looks like:
class DerivedA : public Base
public:
const SomeInfo c_myInfo[2] "NameA1", 1.1f, "NameA2", 1.2f ;
DerivedA() : Base(c_myInfo)
;
This has overhead of two pointers per Base. A vtable uses one pointer, makes your type abstract, adds indirection, and adds one global vtable per Derived type.
Now, in theory, you could get the overhead of this down to the length of the array, and presume that the array data starts right after Base, but that is fragile, non-portable and only useful if desperate.
While you may be rightly leery of templates in embedded code (as you should be of any kind of code generation; code generation means you can generate more than O(1) binary from O(1) code). The span vocabulary type is compact and should be inlined to nothing if your compiler settings are reasonably aggressive.
Start with a vocabulary type:
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);
;
this type has been introduced to C++ std (with slight differences) via the GSL. The basic vocabulary type above is enough if you don't already have it.
A span represents a "pointer" to a block of contiguous objects of known length.
Now we can talk about a 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
;
Now your derived looks like:
class DerivedA : public Base
public:
const SomeInfo c_myInfo[2] "NameA1", 1.1f, "NameA2", 1.2f ;
DerivedA() : Base(c_myInfo)
;
This has overhead of two pointers per Base. A vtable uses one pointer, makes your type abstract, adds indirection, and adds one global vtable per Derived type.
Now, in theory, you could get the overhead of this down to the length of the array, and presume that the array data starts right after Base, but that is fragile, non-portable and only useful if desperate.
While you may be rightly leery of templates in embedded code (as you should be of any kind of code generation; code generation means you can generate more than O(1) binary from O(1) code). The span vocabulary type is compact and should be inlined to nothing if your compiler settings are reasonably aggressive.
edited Jun 18 at 12:52
answered Jun 17 at 18:58
Yakk - Adam NevraumontYakk - Adam Nevraumont
193k21 gold badges210 silver badges399 bronze badges
193k21 gold badges210 silver badges399 bronze badges
add a comment |
add a comment |
So if you really want to keep your data organised the way it is, and I can see why you would in real life:
One way with C++17 would be to return a "view" object representing your content list. This can then be used in a C++11 for statement. You could write a base function that converts start+len into a view, so you don't need to add to the virtual method cruft.
It is not that difficult to create a view object that is compatible with C++11 for statement. Alternatively, you could consider using the C++98 for_each templates that can take a begin and end iterator: Your start iterator is start; the end iterator is start+len.
add a comment |
So if you really want to keep your data organised the way it is, and I can see why you would in real life:
One way with C++17 would be to return a "view" object representing your content list. This can then be used in a C++11 for statement. You could write a base function that converts start+len into a view, so you don't need to add to the virtual method cruft.
It is not that difficult to create a view object that is compatible with C++11 for statement. Alternatively, you could consider using the C++98 for_each templates that can take a begin and end iterator: Your start iterator is start; the end iterator is start+len.
add a comment |
So if you really want to keep your data organised the way it is, and I can see why you would in real life:
One way with C++17 would be to return a "view" object representing your content list. This can then be used in a C++11 for statement. You could write a base function that converts start+len into a view, so you don't need to add to the virtual method cruft.
It is not that difficult to create a view object that is compatible with C++11 for statement. Alternatively, you could consider using the C++98 for_each templates that can take a begin and end iterator: Your start iterator is start; the end iterator is start+len.
So if you really want to keep your data organised the way it is, and I can see why you would in real life:
One way with C++17 would be to return a "view" object representing your content list. This can then be used in a C++11 for statement. You could write a base function that converts start+len into a view, so you don't need to add to the virtual method cruft.
It is not that difficult to create a view object that is compatible with C++11 for statement. Alternatively, you could consider using the C++98 for_each templates that can take a begin and end iterator: Your start iterator is start; the end iterator is start+len.
edited Jun 18 at 10:09
answered Jun 17 at 11:09
Gem TaylorGem Taylor
2,9202 silver badges22 bronze badges
2,9202 silver badges22 bronze badges
add a comment |
add a comment |
How about CRTP + std::array? No extra variables, v-ptr or virtual function calls. std::array is a very thin wrapper around C style array. Empty base class optimization ensures no space is wasted. It looks "elegant" enough to me :)
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 ;
;
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
How about CRTP + std::array? No extra variables, v-ptr or virtual function calls. std::array is a very thin wrapper around C style array. Empty base class optimization ensures no space is wasted. It looks "elegant" enough to me :)
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 ;
;
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
How about CRTP + std::array? No extra variables, v-ptr or virtual function calls. std::array is a very thin wrapper around C style array. Empty base class optimization ensures no space is wasted. It looks "elegant" enough to me :)
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 ;
;
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
How about CRTP + std::array? No extra variables, v-ptr or virtual function calls. std::array is a very thin wrapper around C style array. Empty base class optimization ensures no space is wasted. It looks "elegant" enough to me :)
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 ;
;
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
edited Jun 18 at 14:57
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
answered Jun 18 at 13:21
SPDSPD
312 bronze badges
312 bronze badges
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
New contributor
SPD is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
add a comment |
add a comment |
You can move your data into a two-dimensional array outside of the classes and have each class return an index which contains relevant data.
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();
3
Why aconst std::vectorfor compile-time-constant data with known fixed size? Seems like a job for a flat 1Dstd::array<SomeInfo>with each derived class knowing the right start index + offset. (GetIndex returns astd::pair<int,int>). Or astd::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array ofSomeInfoobjects withnullptrterminators for the end of each sub-array, if you only ever want to iterate in order.
– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point usingvector<vector<T>>overarray<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.
– Peter Cordes
Jun 18 at 15:49
add a comment |
You can move your data into a two-dimensional array outside of the classes and have each class return an index which contains relevant data.
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();
3
Why aconst std::vectorfor compile-time-constant data with known fixed size? Seems like a job for a flat 1Dstd::array<SomeInfo>with each derived class knowing the right start index + offset. (GetIndex returns astd::pair<int,int>). Or astd::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array ofSomeInfoobjects withnullptrterminators for the end of each sub-array, if you only ever want to iterate in order.
– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point usingvector<vector<T>>overarray<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.
– Peter Cordes
Jun 18 at 15:49
add a comment |
You can move your data into a two-dimensional array outside of the classes and have each class return an index which contains relevant data.
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();
You can move your data into a two-dimensional array outside of the classes and have each class return an index which contains relevant data.
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();
edited Jun 18 at 0:01
Peter Mortensen
14.2k19 gold badges88 silver badges114 bronze badges
14.2k19 gold badges88 silver badges114 bronze badges
answered Jun 17 at 8:46
Tanveer BadarTanveer Badar
1,71013 silver badges20 bronze badges
1,71013 silver badges20 bronze badges
3
Why aconst std::vectorfor compile-time-constant data with known fixed size? Seems like a job for a flat 1Dstd::array<SomeInfo>with each derived class knowing the right start index + offset. (GetIndex returns astd::pair<int,int>). Or astd::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array ofSomeInfoobjects withnullptrterminators for the end of each sub-array, if you only ever want to iterate in order.
– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point usingvector<vector<T>>overarray<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.
– Peter Cordes
Jun 18 at 15:49
add a comment |
3
Why aconst std::vectorfor compile-time-constant data with known fixed size? Seems like a job for a flat 1Dstd::array<SomeInfo>with each derived class knowing the right start index + offset. (GetIndex returns astd::pair<int,int>). Or astd::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array ofSomeInfoobjects withnullptrterminators for the end of each sub-array, if you only ever want to iterate in order.
– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point usingvector<vector<T>>overarray<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.
– Peter Cordes
Jun 18 at 15:49
3
3
Why a
const std::vector for compile-time-constant data with known fixed size? Seems like a job for a flat 1D std::array<SomeInfo> with each derived class knowing the right start index + offset. (GetIndex returns a std::pair<int,int>). Or a std::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array of SomeInfo objects with nullptr terminators for the end of each sub-array, if you only ever want to iterate in order.– Peter Cordes
Jun 17 at 21:00
Why a
const std::vector for compile-time-constant data with known fixed size? Seems like a job for a flat 1D std::array<SomeInfo> with each derived class knowing the right start index + offset. (GetIndex returns a std::pair<int,int>). Or a std::array<std::vector<SomeInfo>>. Or maybe implicit lengths by using a flat array of SomeInfo objects with nullptr terminators for the end of each sub-array, if you only ever want to iterate in order.– Peter Cordes
Jun 17 at 21:00
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
OP didn't share full code so it is a coin toss either way about whether the data is compile time constant or not.
– Tanveer Badar
Jun 18 at 8:42
Ok, but there's definitely zero point using
vector<vector<T>> over array<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.– Peter Cordes
Jun 18 at 15:49
Ok, but there's definitely zero point using
vector<vector<T>> over array<vector<T>>. The outer array is definitely fixed-size because it has 1 entry per derived type.– Peter Cordes
Jun 18 at 15:49
add a comment |
Just make the virtual function return a reference to the data directly (you need to change to vector then - not possible with array or C style array types with different sizes):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
or if pointers are the only feasible option, as a pointer range (iterators/range adapter would be preferred though if possible - more on that):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
To make this last method work with range-based for loop: one way is to make a small 'range view' type that has the functions begin()/end() - essential a pair with begin()/end()
Example:
template<class T>
struct ptr_range
std::pair<T*, T*> range_;
auto begin()return range_.begin();
auto end()return range_.end();
;
Then construct it with:
virtual ptr_range<SomeInfo> getDerivedInfo() override
return std::begin(c_myInfo), std::end(c_myInfo);
It is easy to make it non-template if a template is not desired.
add a comment |
Just make the virtual function return a reference to the data directly (you need to change to vector then - not possible with array or C style array types with different sizes):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
or if pointers are the only feasible option, as a pointer range (iterators/range adapter would be preferred though if possible - more on that):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
To make this last method work with range-based for loop: one way is to make a small 'range view' type that has the functions begin()/end() - essential a pair with begin()/end()
Example:
template<class T>
struct ptr_range
std::pair<T*, T*> range_;
auto begin()return range_.begin();
auto end()return range_.end();
;
Then construct it with:
virtual ptr_range<SomeInfo> getDerivedInfo() override
return std::begin(c_myInfo), std::end(c_myInfo);
It is easy to make it non-template if a template is not desired.
add a comment |
Just make the virtual function return a reference to the data directly (you need to change to vector then - not possible with array or C style array types with different sizes):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
or if pointers are the only feasible option, as a pointer range (iterators/range adapter would be preferred though if possible - more on that):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
To make this last method work with range-based for loop: one way is to make a small 'range view' type that has the functions begin()/end() - essential a pair with begin()/end()
Example:
template<class T>
struct ptr_range
std::pair<T*, T*> range_;
auto begin()return range_.begin();
auto end()return range_.end();
;
Then construct it with:
virtual ptr_range<SomeInfo> getDerivedInfo() override
return std::begin(c_myInfo), std::end(c_myInfo);
It is easy to make it non-template if a template is not desired.
Just make the virtual function return a reference to the data directly (you need to change to vector then - not possible with array or C style array types with different sizes):
virtual const std::vector<SomeInfo>& getDerivedInfo() = 0;
or if pointers are the only feasible option, as a pointer range (iterators/range adapter would be preferred though if possible - more on that):
virtual std::pair<SomeInfo*, SomeInfo*> getDerivedInfo() = 0;
To make this last method work with range-based for loop: one way is to make a small 'range view' type that has the functions begin()/end() - essential a pair with begin()/end()
Example:
template<class T>
struct ptr_range
std::pair<T*, T*> range_;
auto begin()return range_.begin();
auto end()return range_.end();
;
Then construct it with:
virtual ptr_range<SomeInfo> getDerivedInfo() override
return std::begin(c_myInfo), std::end(c_myInfo);
It is easy to make it non-template if a template is not desired.
edited Jun 18 at 8:25
answered Jun 17 at 11:35
darunedarune
2,5928 silver badges26 bronze badges
2,5928 silver badges26 bronze badges
add a comment |
add a comment |
SirNobbyNobbs is a new contributor. Be nice, and check out our Code of Conduct.
SirNobbyNobbs is a new contributor. Be nice, and check out our Code of Conduct.
SirNobbyNobbs is a new contributor. Be nice, and check out our Code of Conduct.
SirNobbyNobbs is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56627438%2fhow-do-i-remove-this-inheritance-related-code-smell%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
No, I only instanciate the derived classes.
– SirNobbyNobbs
Jun 17 at 8:47
@NikosC.Base is abstract, can't create instances of it.
– Tanveer Badar
Jun 17 at 8:47
4
If
SomeInfo c_myInfo[3]isconstand has a compile-time constant initializer, why do you have it inside the object instead ofstatic? Do you only create one instance of each type, so there isn't actually duplication of the pointers + floats? (Also a string key/value array doesn't sound great for efficiency if you're using it as a dictionary, but that's a separate issue. Sounds like a job forenum..)– Peter Cordes
Jun 17 at 20:54
2
Thanks for all the suggestions! So far, Nikos C. answer best suits my needs although Peter Cordes approach is also neat and simple. Just some clarifications: 1) Several users suggested to make c_myInfo static const and they are correct of course. 2) My embedded environment isn´t so small that I have to count every bit and byte. I just don´t want to compile some extra 10 kB on libraries if it can be avoided. Readability is more important than code efficiency.
– SirNobbyNobbs
Jun 19 at 6:49