วัตถุประสงค์ของ std :: make_pair เทียบกับนวกรรมิกของ std :: pair คืออะไร?


180

มีจุดประสงค์std::make_pairอะไร?

ทำไมไม่ทำอย่างนั้นstd::pair<int, char>(0, 'a')?

มีความแตกต่างระหว่างสองวิธีหรือไม่?


6
ใน C ++ 11 คุณสามารถทำได้เกือบทั้งหมดโดยไม่ต้อง make_pair ดูคำตอบของฉัน
PlagueHammer

2
ใน C ++ 17 std::make_pairมีการทำซ้ำซ้อน มีคำตอบด้านล่างที่ให้รายละเอียดนี้
Drew Dormann

คำตอบ:


165

ความแตกต่างคือเมื่อstd::pairคุณจำเป็นต้องระบุประเภทขององค์ประกอบทั้งสองในขณะที่std::make_pairจะสร้างคู่ที่มีประเภทขององค์ประกอบที่ส่งผ่านไปโดยที่คุณไม่จำเป็นต้องบอก นั่นคือสิ่งที่ฉันสามารถรวบรวมได้จากเอกสารต่าง ๆ อย่างไรก็ตาม

ดูตัวอย่างนี้จากhttp://www.cplusplus.com/reference/std/utility/make_pair/

pair <int,int> one;
pair <int,int> two;

one = make_pair (10,20);
two = make_pair (10.5,'A'); // ok: implicit conversion from pair<double,char>

นอกเหนือจากโบนัสการแปลงโดยนัยแล้วหากคุณไม่ได้ใช้ make_pair คุณต้องทำ

one = pair<int,int>(10,20)

ทุกครั้งที่คุณกำหนดให้กับหนึ่งซึ่งจะน่ารำคาญตลอดเวลา ...


1
ที่จริงแล้วประเภทควรจะอนุมานได้ในเวลารวบรวมโดยไม่จำเป็นต้องระบุ
ชาด

@Tor std::make_pairใช่ฉันรู้วิธีการใช้ทั้งสองของพวกเขาผมอยากรู้เพียงถ้ามีเหตุผล เห็นได้ชัดว่ามันเป็นเพียงเพื่อความสะดวก

@Jay มันจะปรากฏขึ้นเพื่อ
Tor Valamo

15
ฉันคิดว่าคุณสามารถทำได้one = {10, 20}ทุกวันนี้ แต่ฉันไม่มีคอมไพเลอร์ C ++ 11 ที่สะดวกในการตรวจสอบ
MSalters

6
นอกจากนี้โปรดทราบว่าmake_pairทำงานได้กับประเภทที่ไม่มีชื่อรวมถึง struct, unions, lambdas และ doodads อื่น ๆ
Mooing Duck

35

ดังที่ @MSalters ตอบกลับด้านบนตอนนี้คุณสามารถใช้เครื่องมือจัดฟันแบบหยิกเพื่อทำสิ่งนี้ใน C ++ 11 (เพิ่งยืนยันด้วยคอมไพเลอร์ C ++ 11):

pair<int, int> p = {1, 2};

28

อาร์กิวเมนต์เทมเพลตคลาสไม่สามารถอนุมานจากตัวสร้างได้ก่อน C ++ 17

ก่อน C ++ 17 คุณไม่สามารถเขียนสิ่งที่ชอบ:

std::pair p(1, 'a');

ตั้งแต่นั้นจะอนุมานประเภทแม่แบบจากการสร้างข้อโต้แย้ง

C ++ 17 ทำให้ไวยากรณ์นั้นเป็นไปได้และmake_pairซ้ำซ้อน

ก่อน C ++ 17 std::make_pairอนุญาตให้เราเขียนโค้ด verbose น้อยกว่า:

MyLongClassName1 o1;
MyLongClassName2 o2;
auto p = std::make_pair(o1, o2);

แทน verbose เพิ่มเติม:

std::pair<MyLongClassName1,MyLongClassName2> p{o1, o2};

ซึ่งทำซ้ำประเภทและสามารถยาวมาก

การอนุมานประเภททำงานได้ในกรณีพรี C ++ 17 เนื่องจากmake_pairไม่ใช่ตัวสร้าง

make_pair โดยพื้นฐานแล้วเทียบเท่ากับ:

template<class T1, class T2>
std::pair<T1, T2> my_make_pair(T1 t1, T2 t2) {
    return std::pair<T1, T2>(t1, t2);
}

แนวคิดเดียวกันกับVSinserterinsert_iterator

ดูสิ่งนี้ด้วย:

ตัวอย่างที่น้อยที่สุด

ในการทำให้สิ่งต่าง ๆ เป็นรูปธรรมมากขึ้นเราสามารถสังเกตปัญหาได้น้อยที่สุดด้วย:

main.cpp

template <class MyType>
struct MyClass {
    MyType i;
    MyClass(MyType i) : i(i) {}
};

template<class MyType>
MyClass<MyType> make_my_class(MyType i) {
    return MyClass<MyType>(i);
}

int main() {
    MyClass<int> my_class(1);
}

แล้ว:

g++-8 -Wall -Wextra -Wpedantic -std=c++17 main.cpp

รวบรวมอย่างมีความสุข แต่:

g++-8 -Wall -Wextra -Wpedantic -std=c++14 main.cpp

ล้มเหลวด้วย:

main.cpp: In function int main()’:
main.cpp:13:13: error: missing template arguments before my_class
     MyClass my_class(1);
             ^~~~~~~~

และต้องการทำงานแทน:

MyClass<int> my_class(1);

หรือผู้ช่วย:

auto my_class = make_my_class(1);

ซึ่งใช้ฟังก์ชั่นปกติแทนนวกรรมิก

ความแตกต่างสำหรับ `std :: reference_wrapper

ความคิดเห็นนี้พูดถึงว่าไม่ได้std::make_pairห่อหุ้มstd::reference_wrapperในขณะที่ตัวสร้างไม่ได้ดังนั้นจึงเป็นข้อแตกต่าง ตัวอย่างสิ่งที่ต้องทำ

ทดสอบกับGCC 8.1.0, อูบุนตู 16.04


1
"C ++ 17 ทำให้ไวยากรณ์นั้นเป็นไปได้และทำให้ make_pair ซ้ำซ้อน" - เพราะเหตุใดจึงstd::make_pairไม่เลิกใช้ใน C ++ 17
andreee

@ andreee ฉันไม่แน่ใจว่าเหตุผลที่เป็นไปได้คือมันสร้างปัญหาได้ดังนั้นไม่จำเป็นต้องทำลายรหัสเก่าหรือไม่ แต่ฉันไม่คุ้นเคยกับเหตุผลของคณะกรรมการ C ++ แล้วส่ง Ping ให้ฉันหากคุณพบบางสิ่ง
Ciro Santilli 郝海东冠状病六四事件法轮功

1
สิ่งที่มีประโยชน์อย่างหนึ่งที่ฉันเจอคือความสามารถในการระบุชนิดด้วย std :: make_pair <T1, T2> (o1, o2) ป้องกันผู้ใช้จากการทำผิดประเภทผ่าน o1 หรือ o2 ซึ่งไม่สามารถบอกเป็นนัยได้ ส่งถึง T1 หรือ T2 ตัวอย่างเช่นการส่งจำนวนลบไปยัง int ที่ไม่ได้ลงชื่อ -Wsign-conversion -Werror จะไม่ตรวจจับข้อผิดพลาดนี้ด้วย std :: pair constructor ใน c ++ 11 อย่างไรก็ตามมันจะตรวจจับข้อผิดพลาดหาก std :: make_pair ถูกใช้
conchoecia

make_pairไม่ทำการรวม wrappers อ้างอิงดังนั้นจึงแตกต่างจาก CTAD จริง ๆ
LF

26

ไม่มีความแตกต่างระหว่างการใช้make_pairและการเรียกคอนpairสตรัคเตอร์อย่างชัดเจนด้วยอาร์กิวเมนต์ชนิดที่ระบุ std::make_pairสะดวกยิ่งขึ้นเมื่อชนิดเป็น verbose เนื่องจากวิธีการแม่แบบมีการหักประเภทตามพารามิเตอร์ที่กำหนด ตัวอย่างเช่น,

std::vector< std::pair< std::vector<int>, std::vector<int> > > vecOfPair;
std::vector<int> emptyV;

// shorter
vecOfPair.push_back(std::make_pair(emptyV, emptyV));

 // longer
vecOfPair.push_back(std::pair< std::vector<int>, std::vector<int> >(emptyV, emptyV));

21

เป็นที่น่าสังเกตว่านี่เป็นสำนวนทั่วไปในการเขียนโปรแกรมเทมเพลต C ++ เป็นที่รู้จักกันเป็นวัตถุปั่นไฟสำนวนคุณสามารถหาข้อมูลเพิ่มเติมและตัวอย่างที่ดีที่นี่

แก้ไขตามที่มีคนแนะนำในความคิดเห็น (ตั้งแต่ลบ) ต่อไปนี้เป็นสารสกัดที่แก้ไขเล็กน้อยจากลิงค์ในกรณีที่มันแตก

เครื่องสร้างวัตถุช่วยให้การสร้างวัตถุโดยไม่ต้องระบุประเภทของพวกเขาอย่างชัดเจน มันขึ้นอยู่กับคุณสมบัติที่มีประโยชน์ของฟังก์ชั่นเทมเพลตซึ่งเทมเพลตของคลาสไม่มี: พารามิเตอร์ประเภทของเทมเพลตฟังก์ชั่นนั้นจะถูกอนุมานโดยอัตโนมัติจากพารามิเตอร์จริง std::make_pairเป็นตัวอย่างง่ายๆที่คืนค่าอินสแตนซ์ของstd::pairเทมเพลตโดยขึ้นอยู่กับพารามิเตอร์จริงของstd::make_pairฟังก์ชัน

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}

2
@duck จริงแล้ว&&ตั้งแต่ C ++ 11
Justme0

5

make_pair สร้างสำเนาเพิ่มเติมบนตัวสร้างโดยตรง ฉันพิมพ์คู่ของฉันเสมอเพื่อให้ไวยากรณ์ที่เรียบง่าย
สิ่งนี้แสดงให้เห็นถึงความแตกต่าง (ตัวอย่างโดย Rampal Chaudhary):

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair( 1, sample) );
    //map.insert( std::pair<int,Sample>( 1, sample) );
    return 0;
}

4
ฉันค่อนข้างมั่นใจว่าจะมีการคัดลอกพิเศษในทุกกรณีหากการตั้งค่าการเพิ่มประสิทธิภาพของคอมไพเลอร์สูงพอ
Björn Pollex

1
ทำไมคุณถึงต้องการพึ่งพาการเพิ่มประสิทธิภาพของคอมไพเลอร์เพื่อความถูกต้อง?
sjbx

ฉันจะได้รับผลเช่นเดียวกันกับทั้งสองรุ่นและมีstd::moveเพียงภายในinsertและ / หรือรอบ ๆ sampleสิ่งที่จะมีการอ้างอิงถึง เมื่อฉันเปลี่ยนstd::map<int,Sample>เป็นstd::map<int,Sample const&>ฉันลดจำนวนของวัตถุที่สร้างและเฉพาะเมื่อฉันลบตัวสร้างสำเนาที่ฉันกำจัดสำเนาทั้งหมด (ชัด) หลังจากทำการเปลี่ยนแปลงทั้งสองอย่างแล้วผลลัพธ์ของฉันจะรวมการเรียกหนึ่งไปยังตัวสร้างเริ่มต้นและการเรียกสองครั้งไปยัง destructor สำหรับวัตถุเดียวกัน ฉันคิดว่าฉันต้องคิดถึงบางสิ่งบางอย่าง (g ++ 5.4.1, c ++ 11)
John P

FWIW ฉันยอมรับว่าการเพิ่มประสิทธิภาพและความถูกต้องควรเป็นอิสระอย่างสมบูรณ์เนื่องจากนี่เป็นประเภทของรหัสที่คุณเขียนเป็นการตรวจสอบที่มีสติหลังจากระดับการเพิ่มประสิทธิภาพที่แตกต่างกันทำให้เกิดผลลัพธ์ที่ไม่สอดคล้องกัน โดยทั่วไปแล้วฉันอยากจะแนะนำemplaceแทนที่จะเป็นinsertถ้าคุณเพิ่งสร้างมูลค่าที่จะแทรกทันที (และคุณไม่ต้องการอินสแตนซ์เพิ่มเติม) ไม่ใช่พื้นที่ของความเชี่ยวชาญของฉันถ้าฉันสามารถพูดได้ว่าฉันมีหนึ่ง แต่คัดลอก / ย้าย ความหมายที่แนะนำโดย C ++ 11 ช่วยฉันได้มาก
John P

ฉันเชื่อว่าฉันกำลังประสบปัญหาเดียวกันอย่างแน่นอนและหลังจากการดีบั๊กประมาณเย็นทั้งหมดฉันก็มาที่นี่
lllllllllllll

1

เริ่มต้นจาก c ++ 11 เพียงใช้การเริ่มต้นที่เหมือนกันสำหรับคู่ ดังนั้นแทนที่จะ:

std::make_pair(1, 2);

หรือ

std::pair<int, int>(1, 2);

เพียงแค่ใช้

{1, 2};

{1, 2}สามารถใช้ในการเริ่มต้นคู่ แต่ไม่ได้กระทำสำหรับคู่ประเภท กล่าวคือเมื่อมีการใช้รถยนต์ที่คุณต้องกระทำเพื่อประเภทใน RHS auto p = std::pair{"Tokyo"s, 9.00};นี้:
Markus
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.