1

Let's say i have following classes

class MyClass
{
    private:
        std::string data_;
    public:
        MyClass(const std::string& data) : data_(data) {}
        void func()
        {
            std::cout << data_ << std::endl;
        }
};

class MyClassWrapper
{
    private:
        std::unique_ptr<MyClass> obj_;
    public:
        MyClassWrapper(const MyClass& obj) : obj_(std::make_unique<MyClass>(obj)) {}
        void func()
        {
            obj_->func();
        }
};

and following code.

    MyClassWrapper my_class(MyClass("some message"));
    MyClassWrapper* my_class_ptr = &my_class;
    my_class_ptr->func();

My question is, when i call func() on line my_class_ptr->func();, is the memory location of the object my_class actually accessed when i dereference my_class_ptr? And then, when i call obj_->func(); in the implementation of the function, do I physically access the obj_ field of MyClassWrapper (i mean the pointer, not what it points to)?

The reason why I'm asking this is that if i have objects of type MyClassWrapper in some container, then if the memory of the object is accessed, i would have to block other threads from adding elements to the container, and i want to know if that's necesseary.

5
  • 3
    obj->func() calls func() with obj as this argument. If func() access members, obj is dereferenced/accessed. Commented May 24 at 20:20
  • It depends on the container. For instance a vector can have allocated but unused space in which case threads can access objects in the vector. OTOH, if adding elements reallocates then you should not. These are design decisions and preallocating with vector::reserve can provide flexibility and increased performance. Commented May 24 at 20:30
  • MyClass occupies memory in two ways : Code and data, where data is represented by an (often implicit) this pointer. So if ptr is a pointer to MyClass it actually only points to the member data, and calling a member function f() on an instance is actually more similar to calling a (freestanding) function f(MyClass* this) Commented May 24 at 21:27
  • I would generally be EXTREMELY concerned if someone tried to avoid thread-synchronization based on some idea about how the compiler does things internally. The next compiler might do it differently, or people (or myself, because I forgot that I had this weird idea back then) might change the code so the assumption no longer applies ... In general, if I want to avoid moving objects around, I like to store std::unique_ptr in the containers (sometimes std::shared_ptr) -- the pointer can be moved around freely and efficiently, while the actual data isn't affected at all. Commented May 25 at 9:44
  • @ChristianStieber Yep. That's why one should check container specs to ascertain when iterators are, or are not, invalidated and not based on some belief of compiler behavior. Commented May 25 at 16:33

2 Answers 2

4

under the hood most C++ implementations implement non-virtual functions as

object->function(...)
// somewhat equivalent to
function(object, ...)

unless the function is inlined, the this pointer will be passed to the function as the first argument, therefore it has to be loaded

my_class_ptr->func(); // m_class_ptr must be loaded
obj_->func(); // obj_ must be loaded

virtual functions add an extra twist because they must access the virtual table pointer inside the object and so they must deference the pointer and access the object before the function call is made.


member functions access data members of the object and those data members are loaded. so the object shouldn't be moved while the function is running.

if the function doesn't access the data members then it should be marked static.

Sign up to request clarification or add additional context in comments.

3 Comments

As a follow-up question, if i access some field of a class, like my_class_ptr->some_field;, do i access the memory location of ONLY the field some_field?
@user15955526 the C++ abstract machine memory model doesn't work with accesses. but let's assume bitfields don't exist. you can read and write to two contiguous members concurrently from two threads without synchronization. the compiler has to do the reads and writes in a way that doesn't cause modifications of one object to affect other nearby objects (doesn't apply to bitfields). however, because actual hardware works with cache lines of 64 to 128 bytes wide, you get false sharing and degraded performance.
so, the hardware loads 64 to 128 bytes, and C++ guarantees modifications to one object doesn't affect nearby objects (excluding bitfields).
1

i would have to block other threads from adding elements to the container

This may not be an issue depending on your choice of a container, see the handy comparison table at cppreference

  • vector invalidates references on resize, which can be avoided by using reserve() and checking size() < capacity() before push_back(). pop_back() is also safe (outside of erased element, ofc), while insert() and erase() invalidate everything after the point of insertion/deletion.
  • list or set never invalidate references on any insert() or erase() but are slow to iterate and have larger memory overhead.
  • deque should have comparable performance to vector for most uses, while also supporting insertion and erasure from either end without worrying about capacity. insert() and erase() however invalidate everything, since you don't know which way it will shrink.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.