0

Given a code below

class Base {
public:
    virtual void callThisOnce();
};

class Derived_A: public Base {};
class Derived_B: public Base {};

void function(std::vector<shared_ptr<Base>> v) {
   for (auto i : v)
       i->callThisOnce();
}

Vector v contains either or both shared_ptr<Derived_A> and shared_ptr<Derived_B> objects.
What I want to archive is that when ever function is called, if in v there is a object belong to class Derived_A, Derived_A::callThisOnce should be executed once; and if there is a object belong to class Derived_B, Derived_B::callThisOnce should be executed once.
I need a coding pattern that make it easiest to create Derived_C.
I tried this

class Derived_A: public Base {
    virtual void callThisOnce(){
        if(!_mutex.try_lock())
             return;
        /* Do something */
    }
    static std::mutex _mutex;
};

void function(std::vector<shared_ptr<Base>> v) {
   for (auto i : v)
       i->callThisOnce();
   Derived_A::_mutex.try_lock(); // this call prevent undefined behavior of mutex::unlock
   Derived_A::_mutex.unlock();
}

This pattern make me create a static mutex an required me to call std::mutex::unlock of all class. But it's seem to leave many problems.

Is there a better method to archive the same?

Silver
  • 406
  • 2
  • 12
  • 9
    "*Vector `v` contains either or both `Derived_A` and `Derived_B` objects*" - that's incorrect. It contains **only** `Base` objects due to *object slicing*. I also don't quite understand the "executing once" constraint. If there are multiple objects in the vector, **on which one** should that method be executed? – Fureeish Feb 23 '22 at 14:59
  • object slicing: https://stackoverflow.com/questions/274626/what-is-object-slicing – 463035818_is_not_an_ai Feb 23 '22 at 15:00
  • @OP -- If you have used other languages, where if you have a container of `Base` means that you can also have `Derived` in the same container, C++ doesn't work that way. What seems strange is that you are utilizing something kind of advanced (`std::mutex`), but were not aware of object slicing, which every C++ programmer using intermediate/advanced level concepts should have been aware of. – PaulMcKenzie Feb 23 '22 at 15:02
  • 1
    `void function(std::vector v)` -- Also, a vector is usually passed by reference or const reference, not by value. Honestly, it looks like you're using techniques from one language, and trying to apply it to C++. As you can see, doing that becomes an [all your base are belong to us](https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us) scenario. – PaulMcKenzie Feb 23 '22 at 15:31
  • @PaulMcKenzie Actually I'm using `shared_ptr` for the vector. But when I try to translate the problem into minimal code, I forgot about Object slicing. Seem it has cause some chaos in here – Silver Feb 24 '22 at 03:41

2 Answers2

3
std::vector<Base> v

vector v contains either or both Derived_A and Derived_B objects.

What you describe isn't possible. A vector of Base can only contain objects of type Base and not objects of type Derived_A or Derived_B.

If you want polymorphic storage, then you need indirection. And if you want to combine indirection with the lifetime of the vector, then you need smart pointers. And if you want unique ownership, then you need a virtual destructor:

struct Base {
    virtual void callThisOnce();
    virtual ~Base() = default;
};

struct Derived_A: Base {};
struct Derived_B: Base {};

void function(std::vector<std::unique_ptr<Base>>& v);

To call a member function once per unique dynamic type, you could store each encountered std::type_index in a set and call the function only for the first one:

std::unordered_set<std::type_index> types;
for (auto&& ptr : v) {
   auto [it, first] = types.emplace(typeid(*ptr));
   if (first) {
       ptr->callThisOnce();
   }
eerorika
  • 232,697
  • 12
  • 197
  • 326
0

If I got your intention right one way to achieve this is by combining runtime polymorphism (the correct function gets called) with using a static variable in each class (to track down if function is called only once per class and not per object):

class Base {
public:
    virtual void callThisOnce() {
        if (!base_called) {
            std::cout << "Base::callThisOnce" << std::endl;
            base_called = true;
        }
    };

    static bool base_called;
};

class Derived_A: public Base {
public:
    virtual void callThisOnce() override {
        if (!deriveda_called) {
            std::cout << "Derived_A::callThisOnce" << std::endl;
            deriveda_called = true;
        }
    }

    static bool deriveda_called;
};


class Derived_B: public Base {
public:
    virtual void callThisOnce() override {
        if (!derivedb_called) {
            std::cout << "Derived_B::callThisOnce" << std::endl;
            derivedb_called = true;
        }
    }

    static bool derivedb_called;
};


bool Base::base_called;
bool Derived_A::deriveda_called;
bool Derived_B::derivedb_called;

void function(const std::vector<Base*> &v) {
   Base::base_called = false;
   Derived_A::deriveda_called = false;
   Derived_B::derivedb_called = false;

   for (auto i : v)
       i->callThisOnce();
}

int main() {
    std::vector<Base*> v;
    Base b1, b2;
    Derived_A da1, da2;
    Derived_B db1, db2;
    
    v.push_back(&b1);
    v.push_back(&b2);
    v.push_back(&da1);
    v.push_back(&da2);
    v.push_back(&db1);
    v.push_back(&db2);
    
    function(v);

    return 0;
}

which will output:

Base::callThisOnce
Derived_A::callThisOnce
Derived_B::callThisOnce

EDIT:

The static variables should get reset after the callThisOnce calls to avoid some unwanted behaviour with recursive calls of function (thanks to @Jarod42)

void function(const std::vector<Base*> &v) {
   for (auto i : v)
       i->callThisOnce();
   
   Base::base_called = false;
   Derived_A::deriveda_called = false;
   Derived_B::derivedb_called = false;
}
Odysseus
  • 1,213
  • 4
  • 12
  • Problem might happens if any `T::callThisOnce` calls `void function(const std::vector& v)`. – Jarod42 Feb 23 '22 at 16:18
  • @Jarod42 I am not sure if I got your point. Is your concern about recursive calls of `function` ? Then one might reset the `static` variables outside of `function` – Odysseus Feb 23 '22 at 17:22
  • 1
    Yes, my point is recursion. One way to handle that would be to restore the global values when exiting the function. – Jarod42 Feb 23 '22 at 17:28
  • This is good, but since the `function` can be called in multiple-threading context., which mean more than one `function` can be call at a time. Can this be safe? – Silver Feb 24 '22 at 02:11
  • Guess this would require to guard the access of each `static` variable but hard to tell what would be the best solution for your problem without knowing more of your program – Odysseus Feb 24 '22 at 07:56
  • You misunderstood my proposal, it would be like [that](http://coliru.stacked-crooked.com/a/b35172e4e4455ee9). – Jarod42 Feb 24 '22 at 13:25
  • @Silver: You should have warned that it is for multithreading, (as current solution doesn't fit for multithreading). – Jarod42 Feb 24 '22 at 13:27