簡體   English   中英

為什么將operator。()的定義從.h移到.cpp文件會導致數據爭用?

[英]why is moving the definition of the operator() from .h to .cpp file causing a data race?

在我的代碼中,我具有以下頭文件:

Global.h:

#ifndef GLOBAL_H_
#define GLOBAL_H_
#include <mutex>
namespace
{
    std::mutex outputMutex;
}
#endif

Test.h:

#ifndef TEST_H_
#define TEST_H_
#include"Global.h"
#include<string>
#include<iostream>
class TestClass
{
    std::string name;
public:
    TestClass(std::string n):name{n}{}
    void operator()()
    {
        for (int i=0;i<30;++i)
        {
            std::lock_guard<std::mutex> lock(outputMutex);
            std::cout<<name<<name<<name<<name<<name<<name<<name<<std::endl;
        }
    }
};
#endif 

實際上,Test2.h等於Test1.h,只包含一個名為“ TestClass2”的類,而不是“ TestClass” 我的main.cpp看起來像這樣:

#include<iostream>
#include <thread>
#include "Global.h"
#include "Test.h"
#include "Test2.h"
using namespace std;

int main()
{
    TestClass obj1("Hello");
    TestClass2 obj2("GoodBye");
    thread t1(obj1);
    thread t2(obj2);
    t1.join();
    t2.join();
}

如果我像這樣運行程序,則會得到預期的輸出:

你好你好你好你好你好你好你好你好

要么

GoodByeGoodByeGoodByeGoodByeGoodByeGoodByeGoodBye

到現在為止還挺好。 但是當我將Test.h和Test2.h的()運算符的定義放在源文件Test.cpp和Test2.cpp中時:

(Test.cpp,與Test2.cpp相同):

#include "Test.h"
#include"Global.h"

void TestClass::operator()()
{
    for (int i=0;i<30;++i)
    {
        std::lock_guard<std::mutex> lock(outputMutex);
        std::cout<<name<<name<<name<<name<<name<<name<<name<<std::endl;
    }
}

並因此從頭文件中刪除該定義: void operator()(); 我突然開始偶爾得到這樣的輸出:

GoodByeHelloGoodByeHelloGoodByeHelloGoodByeHelloGoodByeHelloGoodByeHelloGoodByeHello

我不知道為什么互斥量變量outputMutex的鎖定不再起作用,但是我認為它與正在創建的變量的兩個版本有關,但是我很想得到專業的解釋。 我在Cygwin中使用Eclipse。

這是未定義行為和匿名名稱空間的混合。

首先這個:

namespace {
  std::mutex outputMutex;
}

這是一個包含互斥量outputMatrix的匿名名稱空間。 每個源文件都有一個不同的outputMatrix ,因為它具有不同的名稱。

這就是匿名名稱空間的作用。 可以將它們視為“在此處為構建此文件的每個cpp文件生成唯一的guid”。 它們旨在防止鏈接時符號沖突。

class TestClass {
  std::string name;
public:
  // ...
  void operator()() {
    // ...
  }
};

這是一個(隱式) inline TestClass::operator() 它的主體在每個編譯單元中進行編譯。 通過ODR ,每個編譯單元中的主體必須相同 ,否則您的程序格式不正確,無需進行診斷。 (在類定義inline定義的方法是隱式inline ,包含所有這些包)。

它使用來自匿名名稱空間的令牌。 該標記在每個編譯單元中都有不同的含義。 如果有多個編譯單元,則結果為格式錯誤的程序,無需診斷; C ++標准對其行為沒有任何限制1

在這種特殊情況下,從TestClassTestClass2operator()選擇了相同的編譯單元。 因此它使用了相同的互斥量。 這是不可靠的。 局部重建可能會導致其改變,或者說月相。

當您將其放入自己的.cpp文件時,它不再隱式inline 僅存在一個定義,但它們在單獨的編譯單元中。

這兩個不同的編譯單元獲得了不同的outputMatrix互斥量。


1違反該特定規則的最常見影響是鏈接器根據任意標准(可以在構建之間進行更改!)選擇一個實現,而靜默丟棄其余實現。 這不好,因為對構建過程進行無害的更改(添加更多的內核,部分構建等)可能會破壞您的代碼。 不要違反“內聯函數在任何地方都必須具有相同的定義”規則。 這只是最常見的症狀。 您不保證會有任何明智的事情發生。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM