#The Limitations of Smart Pointers?

64 messages · Page 1 of 1 (latest)

honest berry
#
#include <iostream>
#include <memory>

template<typename T>
class Pector {
private:
    int m_size;
    int m_capacity;
    std::unique_ptr<T[]>m_ptr;

public:
    Pector(int capacity) : m_capacity{ capacity }, m_size{capacity}, m_ptr{std::make_unique<T[]>(capacity)} {}

    void print() {
        for (int i{}; i < m_size; i++) {
            std::cout << *(m_ptr.get() + i) << '\n';
        }
    }

    // Function that change a value at an index
    T& operator[](int index) {
        if (index < m_size) {
            return *(m_ptr.get() + index);
        }
    }

    // Delete (sets it to 0 to let the possibility to add an element without recreating an entire array) the last element of the Pector
    void pop() {
        if (m_size > 0) {
            *(m_ptr.get() + (m_size - 1)) = 0;
            m_size--;
        }
    }
    
    void push(T value) {
        if(m_size >= m_capacity) {
            m_size++;
            m_capacity++;
            std::unique_ptr<T[]>tempPtr{ std::make_unique<T[]>(m_capacity) };
            for (int i{}; i < m_capacity; i++) {
                if (i == m_capacity - 1) {
                    *(tempPtr.get() + i) = value;
                }
                else {
                    *(tempPtr.get() + i) = *(m_ptr.get() + i);
                }
            }
            // m_ptr now points to tempPtr
            m_ptr.swap(tempPtr);
            tempPtr.reset();
        }
        else {
            m_size++;
            m_ptr[m_size - 1] = value;
        }
    }
};

what do you guys think of it?
If I would continue to working on my custom vector, raw pointer shouold probably be the right thing, because yea smart pointers have their limitations

gentle musk
#

What's the specific limitation you think they have?

cosmic fog
#

what limitations are you hinting at when it comes to std::unique_ptr<>?

some feelback on the implementaition at a glance...
1. you could probably use std::unique_ptr<std::array<T, N>> instead of a C array if you want the added convenience of knowing it's bounds (the underlying capacity of the backing array), being able to iterate, and everything else it comes with. (edit: this is wrong... can't realloc with std::array)
2. you don't want to grow the underlying array by 1 item each time the new size is going to exceed capacity. it will become extremely slow when you're reallocating the entire buffer and copying the contents from one to the other on every single insert. you should be using a growth formula where you grow it by some factor each time you need to reallocate the backing array.
3. you should be copying the items in bulk, not indually. you want something like a memcpy() from the old to the new array when type std::is_trivially_copyable<T> is true (or something along those lines when it's safe to do so). most implementations will toggle how the data is copied based on T's type traits.

gentle musk
#

N isn't known at compile time

#

So 1 isn't really correct

#

3 isn't correct either because you cannot memcpy stuff in general

cosmic fog
#

well you'd just allocate to some fixed size to start with

honest berry
#

I'll check it out

gentle musk
#

You'd need to type erase it, which isn't really something that makes any sense compared to just storing the size as dynamic

cosmic fog
#

ah yep yeah you're right

#

you can't realloc

gentle musk
#

You can branch on trivially_copyable<T> and memcpy if true

cosmic fog
gentle musk
#

But that's not something I'd worry about in a beginner vector implementation

honest berry
gentle musk
cosmic fog
#

if you're doing this to learn, i think it's worth exploring optimizations like that. especially growth factor of the underlying array. that will be a night and day difference with larger arrays

honest berry
gentle musk
#

Growth factor of the array absolutely

gentle musk
#

Don't assume those restrictions exist

honest berry
gentle musk
#

How do you know they exist if you don't know what they are?

honest berry
#

searched

cosmic fog
#

you should prove that a limitation exists

gentle musk
#

You googled and you read a source that said this but not the reasons?

cosmic fog
#

if the source wasn't cppreference or learncpp it probably wasn't the greatest source of information

honest berry
cosmic fog
#

(if it was gfg, just avoid that site altogether since there's a ton of misinformation on there)

gentle musk
#

unique_ptr doesn't avoid dangling pointer

honest berry
gentle musk
#

I think you should focus on the code and what you're doing now rather than these generalities

#

Yes, memory leaks are not dangling pointers

honest berry
#

they are faster as well

gentle musk
#

Anyhow unique_ptr T[] + size + capacity is more than fine for now

gentle musk
#

Focus on the vector expansion

honest berry
gentle musk
#

Maybe learn to write an iterator

#

Yes, that is the point and the expansion isn't done correctly right now

#

Orlacs 2

honest berry
#

I just need to fix the growth thing + memcpy(), and I think that's good?

gentle musk
#

It's a start

#

Good luck

honest berry
gentle musk
#

Iterators, the rest of the API, constructors, insert, emplace

#

Tons of stuff

honest berry
#

Iterators in the vector?

gentle musk
#

The vectors iterators

honest berry
gentle musk
#

so you could for example use this vector in a range for loop

honest berry
#

thx for the help, I'll update you guys

gentle musk
#

Np

cosmic fog
#

if you're mostly after ideas for learning purposes... definitely do iterators like @gentle musk suggested, that's another must-have imo. some more ideas...

also since you mentioned the memcpy thing above, just keep in mind you need to detect when it's safe to use memcpy vs copy init when moving the data from one array to the other. the type has to naturally be safe to shallow copy (where deep copies would be needed for any complex types). i edited my orginial post above to include some additional context about how you can detect this type of thing using <type_traits>.

  • you could also implement move/copy operators to optimize working with r-value vectors
  • maybe some nice easy initializer constructors that support std::initializer_list<T> as an iniput (so you can do stuff like Pector p{1, 2, 3, 4}; to create a new vector with those elements in it).
  • maybe you can try to implement a sort member function that takes in a lambda as a comparison function that can be used to sort the data in different ways (sort algorithms are always fun to implement.
  • constexpr / compile time functionality
  • benchmarking your implementation vs std::vector and see how close you can get (learning to diagnose / debug / profile for bottlenecks in the process if you've never tried stuff like that before).

once you think you've added enough stuff to the vector, you could try extending what you have in place for this and work it into something that supports working with it as a 2D vector, but all of the data is stored in a 1D array behind the scenes. going down paths to implement stuff like this is usually decent when it comes to learning how to store/access data in an efficient way (like vectors/arrays do - using contiguous blocks of memory). you'll come across some stuff that you though might have been hard, but turned out easy to implement... and some other stuff that can get more challenging (like enforcing a specific number of columns and rows... but supporting the ability to dynamically add columns and rows - while still storing it in your vector class behind the scenes).

there's plenty of stuff like this you can try. data structures are pretty fun to write / benchmark / compare. they are a good way to learn a lot of fundamental CS concepts with practical real-world applications. depending on how deep you are into learning CS data structures and algorithms and C++ in general... some of the stuff mentioned above might not be worth diving into too quickly if they're a bit too complicated for what you're aiming to gradually try implementing.

honest berry