Calling a function only to call vector functions it is inefficient?

Hello, I have a certain class whose a member it is a vector. I made this vector member a private member for prevent unintentionally changes, following Object oriented conventions.
An object of this class was created and it is used in another class. So to access the vector member I need to use functions.
There I need to do two things. One it is to constantly get the size of this vector and the other is to add objects to this vector.
Since he is private, I cannot call directly the push_back() and size() member functions.
So at first I made two functions for the class that owns the vector. The first one pass a const reference to the object to be added to the vector. With this reference in hands, the vector adds it using the push_back member function.
The second calls the size member function of the vector that is then returned by this second function to be available in the other class.

So basically I have an extra level of indirection here, two extra functions to take care of private member limitation. It works but I am afraid that it can generates overhead. I did these two functions inline since they are many times, but I still fear that some overhead might occur.

It is the case? If yes, how should I proceed? To create a method that returns a reference to the vector itself and then directly call the size and push_back member functions? Or there is a better way?

Or am I just wasting my time, since there is not performance loss or it is irrelevant?

Thanks for all the support.
First off, the overhead a single function call, especially if non-virtual, is really not something to worry about unless you have done proper benchmarks and have determined it's actually what's slowing down the program more than any surrounding code.

That being said, the "inline" keyword is almost useless these days and only provides a subtle hint to the compiler. If you have a function that looks like
1
2
3
4
void my_func(a, b, c)
{
    my_other_func(2.0, a, b, c);
}

It is very likely that it will be inlined because it couldn't make executable code bloat.

______________________
Efficiency aside, in my opinion it defeats the purpose of encapsulation if the interface of your class isn't treated as a solid object, so having a method that returns a reference to the vector isn't necessarily a bad thing compared to the alternative of having to re-invent every vector function (size, push_back, etc). But every case is unique, go with what you think provides a better interface to the user of the class.

_______________________
Also, I don't know what your class specifically looks like, but if it's basically just a wrapping around std::vector<Thing>, it might be easier to just use non-class functions, ex:
void do_things_with(std::vector<Thing>& thing_list, double t);

But my suggestions are probably more trouble then it's worth,
tl;dr the compiler will do its job optimizing away function call overhead.
Last edited on
It's probably irrelevant. Nowadays compilers often inline wrapper functions like those. If you look at the disassembly you will probably find that not even std::vector::size() gets called, and instead the private std::vector member is accessed directly.
Thanks for the answers. I would say yes, all it's methods are to wrap vector functions, to get access to the member variables of objects that are added to these vectors and to get references to these objects.

I think I have too many methods, where one function that returns the reference to the vector might be enough.

> The first one pass a const reference to the object to be added to the vector.

In this function, if the object is passed by reference to const, we would make a copy of the object (into the vector).
In general, favour pass by value for such functions, if performance is important. (accept by value and then move).
For rvalues this would be measurably faster, while for lvalues, it wouldn't make much of a difference.

We are relying on the quality of implementation, so measure the performance for typical use cases on the implementation to be used.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
#include <vector>
#include <ctime>

struct moveable
{
    moveable( int v = 0, double d = 0 ) : v(16,v), d(16,d) {}

    std::vector<int> v ;
    std::vector<double> d ;
};

template < typename T > struct A
{
    // pass by reference to const and copy into vector
    void push_back_cref( const T& v ) { vec.push_back(v) ; }
    
    // pass by value and move into vector
    void push_back_value( T v ) { vec.push_back( std::move(v) ) ; }
    
    void pop_back() { vec.pop_back() ; }
    std::size_t size() const { return vec.size() ; }

    std::vector<T> vec ;
};

int main()
{
    constexpr int N = 1'000'000 ;

    const moveable lvalue ;

    A<moveable> a ;
    a.vec.reserve(N) ;

    const auto milliseconds = []( std::clock_t v ) { return v * 1000.0 / CLOCKS_PER_SEC ; } ;

    std::cout << "rvalues:\n----------------\n" ;

    auto start = std::clock() ;
    while( a.vec.size() < N ) a.vec.push_back( { 1, 2 } ) ;
    while( a.vec.size() > 0 ) a.vec.pop_back() ;
    std::cout << "   direct access to vector: " << milliseconds( std::clock() - start ) << '\n' ;

    start = std::clock() ;
    while( a.size() < N ) a.push_back_cref( { 1, 2 } ) ;
    while( a.size() > 0 ) a.pop_back() ;
    std::cout << "lvalue reference semantics: " << milliseconds( std::clock() - start ) << '\n' ;

    start = std::clock() ;
    while( a.size() < N ) a.push_back_value( { 1, 2 } ) ;
    while( a.size() > 0 ) a.pop_back() ;
    std::cout << "           value semantics: " << milliseconds( std::clock() - start ) << '\n' ;

    std::cout << "\nlvalues:\n----------------\n" ;

    start = std::clock() ;
    while( a.vec.size() < N ) a.vec.push_back(lvalue) ;
    while( a.vec.size() > 0 ) a.vec.pop_back() ;
    std::cout << "   direct access to vector: " << milliseconds( std::clock() - start ) << '\n' ;

    start = std::clock() ;
    while( a.size() < N ) a.push_back_cref(lvalue) ;
    while( a.size() > 0 ) a.pop_back() ;
    std::cout << "lvalue reference semantics: " << milliseconds( std::clock() - start ) << '\n' ;

    start = std::clock() ;
    while( a.size() < N ) a.push_back_value(lvalue) ;
    while( a.size() > 0 ) a.pop_back() ;
    std::cout << "           value semantics: " << milliseconds( std::clock() - start ) << '\n' ;
}


clang++ -std=c++14 -stdlib=libc++ -O3 -Wall -Wextra -pedantic-errors main.cpp && ./a.out #> /dev/null && ./a.out
rvalues:
----------------
   direct access to vector: 810
lvalue reference semantics: 1120
           value semantics: 730

lvalues:
----------------
   direct access to vector: 770
lvalue reference semantics: 870
           value semantics: 840

http://coliru.stacked-crooked.com/a/3aacbc4ac96a8512

g++ -std=c++14 -O3 -Wall -Wextra -pedantic-errors main.cpp && ./a.out > /dev/null && ./a.out
rvalues:
----------------
   direct access to vector: 720
lvalue reference semantics: 1050
           value semantics: 770

lvalues:
----------------
   direct access to vector: 800
lvalue reference semantics: 800
           value semantics: 870

http://coliru.stacked-crooked.com/a/f45a7ef726dc95a2
Thanks, although I don't know if these examples would be precisely like my use cases, since I don't declare the variable as const and the vector is not made of another vectors.
To me, it makes no sense to make a member private and then expose access to the member through some functions. The second class needs access to the vector, so give it access, or make the second class a friend of the first.
it makes no sense to make a member private and then expose access to the member through some functions
That way you have control over access to the vector. If in future you decide that only values in specific range can be pushed into vector, you just add code into your push_back() function. No interface change.
Additionally you might not want to be able to change arbitrary data or remove values from vector. So your class works like Adapter for underlying vector.
Standard library actually has several Adapters which gives you access to some functionality of underlying objects, namely stack, queue and priority_queue.
Topic archived. No new replies allowed.