Don't expose your guts, guide visitors :
class A {
public:
// we assume you want read-only versions, if not you can add non-const versions
template< class Func >
void for_each_primary( Func f ) const { for_each_value( f, m_primary ); }
template< class Func >
void for_each_secodary( Func f ) const { for_each_value( f, m_secondary ); }
private:
std::vector<int> m_primary;
std::vector<char> m_secondary;
template< class Func, class Container >
void for_each_value( Func f, const Container& c )
{
for( auto i : c )
f( i );
}
};
int main()
{
A a;
a.for_each_primary( [&]( int value )
{ std::cout << "Primary Value : " << value ; } );
a.for_each_secondary( [&]( int value )
{ std::cout << "Secondary Value : " << value ; } )
}
Note that you could use std::function instead of template parameter if you want to put the implementation in a cpp file, making implementation changes less expensive on compilation times in big projects.
Also, I didn't try to compile it now, but I used a lot this pattern in my open-source projects.
This solution is a C++11 enhancement of B I guess.
HOWEVER
This solution have several issues :
- It requires C++11 to be effective, because it's efficient for the user of you class ONLY if he can use lambda.
- It relies on the fact that the class implementer really know what algorithms precisely are to be available to users. If the user need to do complex manipulations to the numbers, jumping from index to index in an unpredictable way for example, then exposing iterators, a copy of the values OR the values would be better.
In fact, this kind of choice totally depends on what you intend the user to do with this class.
By default I prefer the solution I gave you because it's the most "isolated" one, making sure the class know how it's values can be manipulated by external code. It's a bit like "extensions points".
If it's a map, providing a find function to your class is easy. So I think that's the more sane way to expose data and it's also made available by lambdas.
As said, if you need to make sure the user can manipulate the data as he wish, then providing a copy of the container is the next "isolated" option (maybe with a way to reset the container with the copy after that). If a copy would be expensive, then iterators would be better. If not enough then a reference is acceptable but it's certainly a bad idea.
Now assuming you're using C++11 and don't want to provide algorithms, the most idiomatic way is using iterators this way (only the user code changes) :
class B {
private:
std::vector<int> m_primary;
std::vector<char> m_secondary;
public:
// your code is read-write enabled... make sure it's not const_iterator you want
// also I'm using decltypt to allow changing container type without having to manually change functions signatures
decltype(m_primary)::iterator primary_begin() const;
decltype(m_primary)::iterator primary_end() const;
decltype(m_secondary)::iterator secondary_begin() const;
decltype(m_secondary)::iterator secondary_end() const;
};
int main()
{
B b;
std::for_each( b.primary_begin(), b.primary_end(), []( int& value ) {
// ...
});
std::for_each( b.secondary_begin(), b.secondary_end(), []( double& value ) {
// ...
});
}
It depends on what you're trying to accomplish.
Generally when you use OOP or any other architectural technique you want to leverage the benefits that it's approach facilitates. One of those benefits may be more reuse. Reuse is considered good because it can reduce the overall load of code that must be reviewed,code that must be written, etc.
The OOP architectural paradigm also offers information hiding and encapsulation, important for managing and reducing complexity. Reducing complexity in code is crucial to improving code quality and increasing overall development speed.
In my opinion, one of the best ways to refine your underlying OOP architecture is to begin to build a reference implementation. Try to actually implement the high level functionality, at least conceptually or in pseudo code. Doing this forces you to continue the architectural pattern you begin with the fundamental class definitions. In this way you can get feedback from your implementation attempts about what OOP patterns are working cleanly and clearly to express your higher level functionality and which are hindering those efforts by making things more difficult by enforcing unnecessary conformance or, conversely, allowing excessive, distracting and confusing abstraction.
Known (GOF...) design pattern can help you by providing expert heuristics about which things have worked for many people over many trials.
But ultimately it is your project's requirements which will have to guide the final OOP foundation construction.
The core concept for me is whether a particular architectural approach is going to have a NET benefit or a NET loss. If it takes longer to create a whole boatload of abstraction concepts and work out all the kinks in your inheritance hierarchy, that it would to just do a more quick and dirty approach and fix a few bugs or issues. Well, it's a judgement call.
Also, it depends on what you plan to do, if these classes are intended to be the foundation of another 100,000 lines of OOP class hierarchy definitions, it may be time well spent to thoughtfully refine the abstractions as tightly as possible.
Best Answer
When you want to represent distinct concepts, you should create separate types. Yes, this may come with some boilerplate for operator overloading, but having distinct types may offer significant advantages down the line.
std::vector
is a generic dynamic array, not a mathematical vector (despite borrowing its name). So yes, you should create a separate type.Whether the operations you want are then implemented as members or as free functions is a separate design decision. I recommend reading Effective C++.