2

The push_back function that I implemented:

void push_back(T& num) {
    my_vec[index] = num;
    index++;
}

And the emplace_back function:

template<class... Args>
void emplace_back(Args&&... args) {
    push_back(T(std::forward<Args>(args)...));
}

Do you see any problem with this? If yes then could you please tell me

Also, please let me know how does this work?

Please note: the emplace_back is not my implementation, I took it from other questions as I was looking for a way to implement my own emplace_back.

love_to_code
  • 77
  • 1
  • 6
  • It doesn't accomplish the purpose of `emplace_back`, you either need to make `push_back` work with movable values or make `push_back` call `emplace_back` – Alan Birtles Jun 09 '21 at 09:22
  • The idea of `emplace_back` is to construct an element in-place, in raw memory. You're using just plain assignment, that's not what `emplace_back` is supposed to do even if it were a move assignment. – Evg Jun 09 '21 at 09:26
  • `emplace_back` use something called in placement new so your implementation is wrong. – N0ll_Boy Jun 09 '21 at 09:28
  • It's not my implementation. I had already mentioned that. It was taken from here: https://stackoverflow.com/questions/58881357/how-to-create-an-emplace-back-method – love_to_code Jun 09 '21 at 10:49

2 Answers2

5

Do you see any problem with this?

You aren't really emplacing with this. There's still an assignment.

std::vector<T> doesn't allocate an array of T. It allocates raw memory with the size and alignment of an array of T, and then instantiates objects in that raw memory.

With that in mind, you should probably implement push_back in terms of emplace_back, rather than the other way around.

template <typename T>
class my_vector {
    T * start;
    std::size_t size;
    std::size_t capacity;

    void grow(); // Implementation of this is left as an exercise to the reader

public:
    template <typename... Args>
    reference emplace_back(Args&&... args) {
        if (size == capacity) grow();
        return *new (start + size++) T(std::forward<Args>(args)...);
    }

    reference push_back(const T & t) { return emplace_back(t); }
    reference push_back(T && t) { return emplace_back(std::move(t)); }
}

Also, please let me know how does this work?

template <typename... Args> allows zero or more types to match this template, and then T(std::forward<Args>(args)...) is constructing a T with those arguments, "perfectly forwarding" them, i.e. rvalues are passed as rvalues and lvalues as lvalues.

N.b. because std::vector doesn't new[], you cannot implement something that behaves exactly like std::vector before C++ 20, because it has to be able to return a pointer to an array of T from data without constructing an array of T.

Caleth
  • 52,200
  • 2
  • 44
  • 75
1

The point of emplace_back is to construct an object in place. Your implementation constructs an object then copies it to my_vec.

Your implementation will not work with types that are not copyable. E.g. this won't compile:

Vector<std::thread> v;
v.emplace_back([](){});
v.push_back(std::thread([](){}));

Changing push_back to take it's argument via rvalue reference will fix the issue:

void push_back(T&& num) {
    my_vec[index] = std::move(num);
    index++;
}

template<class... Args>
void emplace_back(Args&&... args) {
    push_back(T(std::forward<Args>(args)...));
}

I think however that most standard library implementations are implemented using emplace_back as the lowest level function:

void push_back(T&& num) {
    emplace_back(std::move(num));
}

template<class... Args>
void emplace_back(Args&&... args) {
    my_vec[index] = T(std::forward<Args>(args)...);
    index++;
}

This then makes it easier to implement the push_back overload that copies the values:

void push_back(const T& num) {
    emplace_back(num);
}

Note that this implementation is using move assignment which is still not quite the intention of emplace_back which requires constructing an object in place using placement new on uninitialised memory but assuming my_vec is an array of objects or similar its the best you can do (without fully implementing the semantics of std::vector which is fairly complex).

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60