Я создаю класс, в котором есть метод, который запускает несколько потоков функций-членов в том же классе. Я новичок в потоках на С++, особенно когда задействованы классы, но это то, что я придумал.
class A
{
public:
void StartThreads()
{
std::thread fooThread(&A::FooFunc, this);
fooThread.join();
}
protected:
virtual void FooFunc()
{
while (true)
std::cout << "hello\n";
}
};
Мой вопрос в том, могу ли я получить имя текущего объекта, потому что теперь, если я создам класс B, который наследуется от A, но перезаписывает FooFunc, FooFunc из класса A будет вызываться, когда я это делаю:
B b;
b.StartThreads();
Поэтому я ищу способ заменить std::thread fooThread(&A::FooFunc, this)
на что-то вроде std::thread fooThread(&this->GetClass()::FooFunc, this)
. Я мог бы просто сделать StartThreads
виртуальным и перезаписать его в производных классах, но было бы лучше просто написать его один раз и покончить с этим. Есть ли способ сделать это или что-то, что приводит к тому же самому?
В случае, если ваш this
известен во время компиляции, вам поможет статическое метапрограммирование.
C++, Swift и Rust (а теперь еще и Scala) — это статические языки, в которых есть много трюков во время компиляции для решения подобных проблем.
Как? В вашем случае шаблоны может вам помочь.
Кроме того, вам не нужно, чтобы это была функция-член, это может быть функция друга (чтобы вы могли легко использовать шаблоны).
class A
{
public:
template<typename T>
friend void StartThreads(const T& obj);
protected:
virtual void FooFunc()
{
while (true)
std::cout << "hello\n";
}
};
template<typename T>
void StartThreads(const T& obj) {
std::thread fooThread(&T::FooFunc, obj);
fooThread.join();
}
ПРЕДУПРЕЖДЕНИЕ: Этот ТОЛЬКО работает, если класс известен во время компиляции, т.е.
class B: public A {
};
...
B b;
A &a = b;
StartThreads(a); // Will call it AS IF IT IS A, NOT B
Другое решение:
Функциональное программирование на помощь, вы можете использовать лямбда-выражения (или функторы, использующие структуры, если вы используете C++ до C++11)
С++ 11:
void StartThreads()
{
std::thread fooThread([=](){ this->FooFunc(); });
fooThread.join();
}
С++ 98:
// Forward declaration
class A;
// The functor class (the functor is an object that is callable (i.e. has the operator (), which is the call operator overloaded))
struct ThreadContainer {
private:
A &f;
public:
ThreadContainer(A &f): f(f) {}
void operator() ();
};
class A
{
public:
// To allow access of the protected FooFunc function
friend void ThreadContainer::operator() ();
void StartThreads()
{
// Create the functor
ThreadContainer a(*this);
// Start the thread with the "call" operator, the implementation of the constructor tries to "call" the operand, which here is a
std::thread fooThread(a);
fooThread.join();
}
protected:
virtual void FooFunc()
{
while (true)
std::cout << "hello\n";
}
};
class B: public A {
protected:
virtual void FooFunc() {
while(true)
std::cout << "overridden\n";
}
};
void ThreadContainer::operator() () {
f.FooFunc();
}
Вы рассматривали использование виртуального FooFunc()
напрямую и каким-то образом предположили, что это не работает. (Я не буду говорить о точности этого здесь, поскольку это поднимается в комментариях к вопросу.) Вам не нравится идея перемещения виртуальной функции на более раннем этапе процесса. Так почему бы не перенести его позже? Существует довольно распространенная парадигма, в которой используются невиртуальные оболочки для виртуальных функций. (Обычно оболочка общедоступна, а виртуальная функция защищена или закрыта.) Итак, что-то вроде:
class A
{
public:
void StartThreads()
{
std::thread fooThread(&A::FooFuncCaller, this); // <-- call the new function
fooThread.join();
}
protected:
void FooFuncCaller() // <-- new function layer
{
FooFunc();
}
virtual void FooFunc()
{
while (true)
std::cout << "hello\n";
}
};
Конечно, если прямой вызов виртуального Foofunc
работает, можно использовать и его. Тем не менее, это проще, чем использование шаблонов или пользовательских классов функторов. Лямбда - это разумная альтернатива, преимущество которой заключается в том, что вы не меняете интерфейс вашего класса (заголовочный файл).
Спасибо за все ваши ответы, оказалось, что мой вопрос не имел отношения к делу и что я запутал некоторых других участников в классе.
Спасибо за ваши ответы, которые дали мне некоторое представление о других способах, которыми вы можете сделать то же самое, используя разные методы. (https://stackoverflow.com/users/9335240/user9335240)
Разве этот код уже не делает то, что вы хотите? он вызывает производный, если вы вызываете StartThreads в производном классе