C++ 在构造函数中使用 this 指针

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2516960/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:45:56  来源:igfitidea点击:

C++ using this pointer in constructors

c++constructormultithreadingthis

提问by gilbertc

In C++, during a class constructor, I started a new thread with thispointer as a parameter which will be used in the thread extensively (say, calling member functions). Is that a bad thing to do? Why and what are the consequences?

在 中C++,在类构造函数期间,我启动了一个新线程,将this指针作为参数,该线程将在线程中广泛使用(例如,调用成员函数)。这样做是件坏事吗?为什么以及后果是什么?

My thread start process is at the end of the constructor.

我的线程启动过程在构造函数的末尾。

采纳答案by David Rodríguez - dribeas

The consequence is that the thread can start and code will start executing a not yet fully initialized object. Which is bad enough in itself.

结果是线程可以启动并且代码将开始执行尚未完全初始化的对象。这本身就够糟糕了。

If you are considering that 'well, it will be the last sentence in the constructor, it will be just about as constructed as it gets...' think again: you might derive from that class, and the derived object will not be constructed.

如果您正在考虑“好吧,它将是构造函数中的最后一句话,它将与它所构造的一样......”再想一想:您可能从该类派生,并且不会构造派生对象.

The compiler may want to play with your code around and decide that it will reorder instructions and it might actually pass the thispointer before executing any other part of the code... multithreading is tricky

编译器可能想要处理您的代码并决定它将重新排序指令并且它可能实际上this在执行代码的任何其他部分之前传递指针......多线程很棘手

回答by Steve Jessop

Main consequence is that the thread might start running (and using your pointer) before the constructor has completed, so the object may not be in a defined/usable state. Likewise, depending how the thread is stopped it might continue running after the destructor has started and so the object again may not be in a usable state.

主要后果是线程可能在构造函数完成之前开始运行(并使用您的指针),因此对象可能未处于已定义/可用状态。同样,根据线程的停止方式,它可能会在析构函数启动后继续运行,因此该对象可能不再处于可用状态。

This is especially problematic if your class is a base class, since the derived class constructor won't even start running until after your constructor exits, and the derived class destructor will have completed before yours starts. Also, virtual function calls don't do what you might think before derived classes are constructed and after they're destructed: virtual calls "ignore" classes whose part of the object doesn't exist.

如果您的类是基类,这尤其成问题,因为派生类构造函数甚至在您的构造函数退出之后才会开始运行,并且派生类析构函数将在您的构造函数开始之前完成。此外,虚函数调用不会像您在构造派生类之前和销毁派生类之后所做的那样:虚调用“忽略”对象部分不存在的类。

Example:

例子:

struct BaseThread {
    MyThread() {
        pthread_create(thread, attr, pthread_fn, static_cast<void*>(this));
    }
    virtual ~MyThread() {
        maybe stop thread somehow, reap it;
    }
    virtual void id() { std::cout << "base\n"; }
};

struct DerivedThread : BaseThread {
    virtual void id() { std::cout << "derived\n"; }
};

void* thread_fn(void* input) {
    (static_cast<BaseThread*>(input))->id();
    return 0;
}

Now if you create a DerivedThread, it's a best a race between the thread that constructs it and the new thread, to determine which version of id()gets called. It could be that something worse can happen, you'd need to look quite closely at your threading API and compiler.

现在,如果您创建一个 DerivedThread,最好在构造它的线程和新线程之间进行一场竞赛,以确定id()调用的哪个版本。可能会发生更糟糕的事情,您需要非常仔细地查看您的线程 API 和编译器。

The usual way to not have to worry about this is just to give your thread class a start()function, which the user calls after constructing it.

通常不用担心这个的方法就是给你的线程类一个start()函数,用户在构造它之后调用它。

回答by CB Bailey

It can be potentially dangerous.

它可能具有潜在危险。

During construction of a base class any calls to virtual functions will not despatch to overrides in more derived classes that haven't yet been completely constructed; once the construction of the more derived classes change this changes.

在基类的构造过程中,对虚函数的任何调用都不会发送到尚未完全构造的更多派生类中的覆盖;一旦更多派生类的构造发生变化,这种情况就会发生变化。

If the thread that you kick-off calls a virtual function and it is indeterminate where this happens in relation to the completion of the construction of the class then you are likely to get unpredictable behaviour; perhaps a crash.

如果您启动的线程调用了一个虚函数,并且与类的构造完成相关的发生这种情况的位置不确定,那么您可能会出现不可预测的行为;也许是崩溃。

Without virtual functions, if the thread only uses methods and data of the parts of the class that have been constructed completely the behaviour is likely to be predictable.

在没有虚函数的情况下,如果线程只使用类中已经完全构建的部分的方法和数据,那么行为很可能是可预测的。

回答by Peter Ruderman

I'd say that, as a general rule, you should avoid doing this. But you can certainly get away with it in many circumstances. I think there are basically two things that can go wrong:

我会说,作为一般规则,你应该避免这样做。但在许多情况下,您当然可以逃脱惩罚。我认为基本上有两件事可能会出错:

  1. The new thread might try to access the object before the constructor finishes initializing it. You can work around this by making sure all initialization is complete before you start the thread. But what if someone inherits from your class? You have no control over what their constructor will do.
  2. What happens if your thread fails to start? There isn't really a clean way to handle errors in a constructor. You can throw an exception, but this is perilous since it means that your object's destructor will not get called. If you elect not to throw an exception, then you're stuck writing code in your various methods to check if things were initialized properly.
  1. 新线程可能会在构造函数完成初始化之前尝试访问该对象。您可以通过确保在启动线程之前完成所有初始化来解决此问题。但是如果有人继承了你的类呢?你无法控制他们的构造函数会做什么。
  2. 如果您的线程无法启动会发生什么?没有真正干净的方法来处理构造函数中的错误。您可以抛出异常,但这是危险的,因为这意味着您的对象的析构函数不会被调用。如果您选择不抛出异常,那么您将不得不在各种方法中编写代码来检查事物是否已正确初始化。

Generally speaking, if you have complex, error-prone initialization to perform, then it's best to do it in a method rather than the constructor.

一般来说,如果您要执行复杂的、容易出错的初始化,那么最好在方法中而不是在构造函数中进行。

回答by sbi

Basically, what you need is two-phase construction: You want to start your thread only afterthe object is fully constructed. John Dibling answereda similar (not a duplicate) question yesterday exhaustively discussing two-phase construction. You might want to have a look at it.

基本上,您需要的是两阶段构建:您只想在对象完全构建启动线程。John Dibling 昨天回答了一个类似的(不是重复的)问题,详尽地讨论了两阶段建设。你可能想看看它。

Note, however, that this still leaves the problem that the thread might be started before a derived class' constructor is done. (Derived classes' constructors are called after those of their base classes.)

但是,请注意,这仍然存在线程可能在派生类的构造函数完成之前启动的问题。(派生类的构造函数在其基类的构造函数之后调用。)

So in the end the safest thing is probably to manually start the thread:

所以最后最安全的可能是手动启动线程:

class Thread { 
  public: 
    Thread();
    virtual ~Thread();
    void start();
    // ...
};

class MyThread : public Thread { 
  public:
    MyThread() : Thread() {}
    // ... 
};

void f()
{
  MyThread thrd;
  thrd.start();
  // ...
}

回答by Etienne de Martel

Depends on what you do after starting the thread. If you perform initialization work afterthe thread has started, then it could use data that is not properly initialized.

取决于您启动线程后的操作。如果在线程启动执行初始化工作,则它可能使用未正确初始化的数据。

You can reduce the risks by using a factory method that first creates an object, then starts the thread.

您可以通过使用先创建对象然后启动线程的工厂方法来降低风险。

But I think the greatest flaw in the design is that, for me at least, a constructor that does more than "construction" seems quite confusing.

但我认为设计中最大的缺陷是,至少对我来说,一个不仅仅是“构造”的构造函数似乎很混乱。

回答by i_am_jorf

It's fine, as long as you can start using that pointer right away. If you require the rest of the constructor to complete initialization before the new thread can use the pointer, then you need to do some synchronization.

没关系,只要您可以立即开始使用该指针。如果你需要构造函数的其余部分在新线程可以使用指针之前完成初始化,那么你需要做一些同步。

回答by nagarajan

Some people feel you should not use the thispointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the{body} and even in the initialization list) if you are careful.

有些人认为你不应该this在构造函数中使用指针,因为对象还没有完全形成。但是,如果您小心的话,您可以在构造函数中(在 {body} 中,甚至在初始化列表中)使用它。

Here is something that always works: the {body}of a constructor (or a function called from the constructor) can reliably access the data members declared in a base class and/or the data members declared in the constructor's own class. This is because all those data members are guaranteed to have been fully constructed by the time the constructor's {body} starts executing.

这是始终有效的东西:{body}构造函数(或从构造函数调用的函数)的 可以可靠地访问基类中声明的数据成员和/或构造函数自己的类中声明的数据成员。这是因为所有这些数据成员都保证在构造函数的 {body} 开始执行时已经完全构造。

Here is something that never works: the {body} of a constructor (or a function called from the constructor) cannot get down to a derived class by calling a virtualmember function that is overridden in the derived class. If your goal was to get to the overridden function in the derived class, you won't get what you want. Note that you won't get to the override in the derived class independent of how you call the virtual member function: explicitly using the this pointer (e.g., this->method()), implicitly using the this pointer (e.g., method()), or even calling some other function that calls the virtual member function on your this object. The bottom line is this: even if the caller is constructing an object of a derived class, during the constructor of the base class, your object is not yet of that derived class. You have been warned.

这里有一些永远行不通的事情:构造函数(或从构造函数调用的函数)的 {body} 无法通过调用派生类中覆盖的虚拟成员函数来深入到派生类。如果您的目标是获得派生类中的重写函数,您将不会得到您想要的。请注意,无论您如何调用虚成员函数,您都不会获得派生类中的覆盖:显式使用 this 指针(例如,this->method()),隐式使用 this 指针(例如,method( )),或者甚至调用一些其他函数来调用 this 对象上的虚成员函数。底线是这样的:即使调用者正在构造派生类的对象,在基类的构造函数期间,您的对象还不是该派生类的对象。你被警告了。

Here is something that sometimes works: if you pass any of the data members in this object to another data member's initializer, you must make sure that the other data member has already been initialized. The good news is that you can determine whether the other data member has (or has not) been initialized using some straightforward language rules that are independent of the particular compiler you're using. The bad news is that you have to know those language rules (e.g., base class sub-objects are initialized first (look up the order if you have multiple and/or virtual inheritance!), then data members defined in the class are initialized in the order in which they appear in the class declaration). If you don't know these rules, then don't pass any data member from the this object (regardless of whether or not you explicitly use the thiskeyword) to any other data member's initializer! And if you do know the rules, please be careful.

以下是有时有效的方法:如果将此对象中的任何数据成员传递给另一个数据成员的初始值设定项,则必须确保其他数据成员已被初始化。好消息是,您可以使用一些与您使用的特定编译器无关的简单语言规则来确定其他数据成员是否已(或尚未)初始化。坏消息是你必须知道那些语言规则(例如,基类子对象首先被初始化(如果你有多重和/或虚拟继承,请查看顺序!),然后在类中定义的数据成员在它们出现在类声明中的顺序)。如果你不知道这些规则,那就不要 t 将 this 对象中的任何数据成员(无论您是否明确使用 this 关键字)传递给任何其他数据成员的初始化程序!如果你知道规则,请小心。