C ++ Tuple เทียบกับโครงสร้าง


96

มีความแตกต่างระหว่างการใช้ a std::tupleและ data-only structหรือไม่?

typedef std::tuple<int, double, bool> foo_t;

struct bar_t {
    int id;
    double value;
    bool dirty;
}

จากสิ่งที่ฉันพบทางออนไลน์ฉันพบว่ามีความแตกต่างที่สำคัญสองประการstructคืออ่านได้มากขึ้นในขณะที่tupleมีฟังก์ชันทั่วไปมากมายที่สามารถใช้ได้ ควรมีความแตกต่างของประสิทธิภาพอย่างมีนัยสำคัญหรือไม่? นอกจากนี้เค้าโครงข้อมูลยังใช้งานร่วมกันได้ (หล่อสลับกัน)


ฉันเพิ่งตั้งข้อสังเกตว่าฉันลืมไปแล้วเกี่ยวกับคำถามนักแสดง : การนำไปใช้งานtupleคือการกำหนดการนำไปใช้งานดังนั้นจึงขึ้นอยู่กับการนำไปใช้ของคุณ โดยส่วนตัวผมไม่ขอนับมัน
Matthieu M.

คำตอบ:


32

เรามีการอภิปรายที่คล้ายกันเกี่ยวกับทูเพิลและโครงสร้างและฉันเขียนเกณฑ์มาตรฐานง่ายๆด้วยความช่วยเหลือจากเพื่อนร่วมงานคนหนึ่งของฉันเพื่อระบุความแตกต่างในแง่ของประสิทธิภาพระหว่างทูเพิลและโครงสร้าง ก่อนอื่นเราเริ่มต้นด้วยโครงสร้างเริ่มต้นและทูเพิล

struct StructData {
    int X;
    int Y;
    double Cost;
    std::string Label;

    bool operator==(const StructData &rhs) {
        return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
    }

    bool operator<(const StructData &rhs) {
        return X < rhs.X || (X == rhs.X && (Y < rhs.Y || (Y == rhs.Y && (Cost < rhs.Cost || (Cost == rhs.Cost && Label < rhs.Label)))));
    }
};

using TupleData = std::tuple<int, int, double, std::string>;

จากนั้นเราใช้ Celero เพื่อเปรียบเทียบประสิทธิภาพของโครงสร้างและทูเพิลอย่างง่ายของเรา ด้านล่างนี้คือโค้ดมาตรฐานและผลการทำงานที่รวบรวมโดยใช้ gcc-4.9.2 และ clang-4.0.0:

std::vector<StructData> test_struct_data(const size_t N) {
    std::vector<StructData> data(N);
    std::transform(data.begin(), data.end(), data.begin(), [N](auto item) {
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dis(0, N);
        item.X = dis(gen);
        item.Y = dis(gen);
        item.Cost = item.X * item.Y;
        item.Label = std::to_string(item.Cost);
        return item;
    });
    return data;
}

std::vector<TupleData> test_tuple_data(const std::vector<StructData> &input) {
    std::vector<TupleData> data(input.size());
    std::transform(input.cbegin(), input.cend(), data.begin(),
                   [](auto item) { return std::tie(item.X, item.Y, item.Cost, item.Label); });
    return data;
}

constexpr int NumberOfSamples = 10;
constexpr int NumberOfIterations = 5;
constexpr size_t N = 1000000;
auto const sdata = test_struct_data(N);
auto const tdata = test_tuple_data(sdata);

CELERO_MAIN

BASELINE(Sort, struct, NumberOfSamples, NumberOfIterations) {
    std::vector<StructData> data(sdata.begin(), sdata.end());
    std::sort(data.begin(), data.end());
    // print(data);

}

BENCHMARK(Sort, tuple, NumberOfSamples, NumberOfIterations) {
    std::vector<TupleData> data(tdata.begin(), tdata.end());
    std::sort(data.begin(), data.end());
    // print(data);
}

ผลการปฏิบัติงานที่รวบรวมด้วย clang-4.0.0

Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort            | struct          | Null            |              10 |               5 |         1.00000 |    196663.40000 |            5.08 | 
Sort            | tuple           | Null            |              10 |               5 |         0.92471 |    181857.20000 |            5.50 | 
Complete.

และผลการปฏิบัติงานที่รวบรวมโดยใช้ gcc-4.9.2

Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort            | struct          | Null            |              10 |               5 |         1.00000 |    219096.00000 |            4.56 | 
Sort            | tuple           | Null            |              10 |               5 |         0.91463 |    200391.80000 |            4.99 | 
Complete.

จากผลลัพธ์ข้างต้นเราจะเห็นได้ชัดเจนว่า

  • Tuple เร็วกว่าโครงสร้างเริ่มต้น

  • ผลผลิตไบนารีโดย clang มีประสิทธิภาพสูงกว่า gcc clang-vs-gcc ไม่ใช่จุดประสงค์ของการสนทนานี้ดังนั้นฉันจะไม่ลงลึกในรายละเอียด

เราทุกคนรู้ดีว่าการเขียนตัวดำเนินการ a == หรือ <หรือ> สำหรับคำจำกัดความของโครงสร้างทุกตัวจะเป็นงานที่เจ็บปวดและมีปัญหา ให้แทนที่ตัวเปรียบเทียบที่กำหนดเองของเราโดยใช้ std :: tie และเรียกใช้เกณฑ์มาตรฐานของเราใหม่

bool operator<(const StructData &rhs) {
    return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
}

Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort            | struct          | Null            |              10 |               5 |         1.00000 |    200508.20000 |            4.99 | 
Sort            | tuple           | Null            |              10 |               5 |         0.90033 |    180523.80000 |            5.54 | 
Complete.

ตอนนี้เราจะเห็นแล้วว่าการใช้ std :: tie ทำให้โค้ดของเราดูหรูหรามากขึ้นและยากที่จะทำผิดอย่างไรก็ตามประสิทธิภาพจะลดลงประมาณ 1% ฉันจะอยู่กับโซลูชัน std :: tie ในตอนนี้เนื่องจากฉันได้รับคำเตือนเกี่ยวกับการเปรียบเทียบตัวเลขทศนิยมกับตัวเปรียบเทียบที่กำหนดเอง

จนถึงขณะนี้เรายังไม่มีวิธีแก้ปัญหาใด ๆ ที่จะทำให้รหัส struct ของเราทำงานได้เร็วขึ้น ลองดูที่ฟังก์ชัน swap และเขียนใหม่เพื่อดูว่าเราจะได้รับประสิทธิภาพหรือไม่:

struct StructData {
    int X;
    int Y;
    double Cost;
    std::string Label;

    bool operator==(const StructData &rhs) {
        return std::tie(X,Y,Cost, Label) == std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
    }

    void swap(StructData & other)
    {
        std::swap(X, other.X);
        std::swap(Y, other.Y);
        std::swap(Cost, other.Cost);
        std::swap(Label, other.Label);
    }  

    bool operator<(const StructData &rhs) {
        return std::tie(X,Y,Cost, Label) < std::tie(rhs.X, rhs.Y, rhs.Cost, rhs.Label);
    }
};

ผลการดำเนินงานที่รวบรวมโดยใช้ clang-4.0.0

Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort            | struct          | Null            |              10 |               5 |         1.00000 |    176308.80000 |            5.67 | 
Sort            | tuple           | Null            |              10 |               5 |         1.02699 |    181067.60000 |            5.52 | 
Complete.

และผลการปฏิบัติงานที่รวบรวมโดยใช้ gcc-4.9.2

Celero
Timer resolution: 0.001000 us
-----------------------------------------------------------------------------------------------------------------------------------------------
     Group      |   Experiment    |   Prob. Space   |     Samples     |   Iterations    |    Baseline     |  us/Iteration   | Iterations/sec  | 
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort            | struct          | Null            |              10 |               5 |         1.00000 |    198844.80000 |            5.03 | 
Sort            | tuple           | Null            |              10 |               5 |         1.00601 |    200039.80000 |            5.00 | 
Complete.

ตอนนี้โครงสร้างของเราเร็วกว่าทูเปิลเล็กน้อยในขณะนี้ (ประมาณ 3% โดยมีเสียงดังและน้อยกว่า 1% เมื่อใช้ gcc) อย่างไรก็ตามเราจำเป็นต้องเขียนฟังก์ชัน swap ที่กำหนดเองสำหรับโครงสร้างทั้งหมดของเรา


24

หากคุณใช้ tuples ที่แตกต่างกันหลายตัวในโค้ดของคุณคุณสามารถหลีกเลี่ยงการย่อจำนวน functors ที่คุณใช้ ฉันพูดแบบนี้เพราะฉันมักใช้รูปแบบของ functors ต่อไปนี้:

template<int N>
struct tuple_less{
    template<typename Tuple>
    bool operator()(const Tuple& aLeft, const Tuple& aRight) const{
        typedef typename boost::tuples::element<N, Tuple>::type value_type;
        BOOST_CONCEPT_REQUIRES((boost::LessThanComparable<value_type>));

        return boost::tuples::get<N>(aLeft) < boost::tuples::get<N>(aRight);
    }
};

นี้อาจจะดูเหมือน overkill แต่สำหรับแต่ละสถานที่ภายใน struct ที่ฉันจะต้องทำให้วัตถุ functor ใหม่ทั้งหมดโดยใช้ struct แต่สำหรับ tuple Nผมเพียงแค่การเปลี่ยนแปลง ที่ดีไปกว่านั้นฉันสามารถทำสิ่งนี้กับทุกๆทูเปิลแทนที่จะสร้าง functor ใหม่ทั้งหมดสำหรับโครงสร้างแต่ละตัวและสำหรับตัวแปรสมาชิกแต่ละตัว ถ้าฉันมีโครงสร้าง N ที่มีตัวแปรสมาชิก M ที่ตัวควบคุม NxM ฉันจะต้องสร้าง (สถานการณ์กรณีที่แย่กว่า) ที่สามารถย่อเป็นรหัสเล็กน้อยได้

โดยปกติแล้วหากคุณจะไปกับทาง Tuple คุณจะต้องสร้าง Enums เพื่อทำงานร่วมกับพวกเขาด้วย:

typedef boost::tuples::tuple<double,double,double> JackPot;
enum JackPotIndex{
    MAX_POT,
    CURRENT_POT,
    MIN_POT
};

และบูมรหัสของคุณสามารถอ่านได้อย่างสมบูรณ์:

double guessWhatThisIs = boost::tuples::get<CURRENT_POT>(someJackPotTuple);

เพราะมันอธิบายตัวเองเมื่อคุณต้องการรับรายการที่มีอยู่ภายใน


8
เอ่อ ... C ++ มีพอยน์เตอร์ฟังก์ชันจึงtemplate <typename C, typename T, T C::*> struct struct_less { template <typename C> bool operator()(C const&, C const&) const; };น่าจะทำได้ การสะกดออกนั้นสะดวกน้อยกว่าเล็กน้อย แต่เขียนเพียงครั้งเดียว
Matthieu M.

17

Tuple ได้สร้างขึ้นในค่าเริ่มต้น (สำหรับ == และ! = มันเปรียบเทียบทุกองค์ประกอบสำหรับ <. <= ... เปรียบเทียบก่อนถ้าเหมือนกันเปรียบเทียบวินาที ... ): http://en.cppreference.com/w/ cpp / ยูทิลิตี้ / tuple / operator_cmp

แก้ไข: ตามที่ระบุไว้ในความคิดเห็นตัวดำเนินการยานอวกาศ C ++ 20 ช่วยให้คุณสามารถระบุฟังก์ชันนี้ด้วยโค้ดบรรทัดเดียว (น่าเกลียด แต่ยังคงเป็นเพียงบรรทัดเดียว)


1
ใน C ++ 20 นี้แก้ได้ด้วยสำเร็จรูปน้อยที่สุดโดยใช้ผู้ประกอบการยานอวกาศ
John McFarlane

6

นี่คือเกณฑ์มาตรฐานที่ไม่ได้สร้าง tuples จำนวนมากภายในตัวดำเนินการ struct == () ปรากฎว่ามีผลกระทบด้านประสิทธิภาพที่สำคัญมากจากการใช้ทูเปิลอย่างที่ใคร ๆ คาดหวังเนื่องจากไม่มีผลกระทบด้านประสิทธิภาพจากการใช้ POD เลย (ตัวแก้ไขที่อยู่พบค่าในไปป์ไลน์คำสั่งก่อนที่หน่วยตรรกะจะเห็นด้วยซ้ำ)

ผลลัพธ์ทั่วไปจากการเรียกใช้สิ่งนี้บนเครื่องของฉันด้วย VS2015CE โดยใช้การตั้งค่าเริ่มต้น 'Release':

Structs took 0.0814905 seconds.
Tuples took 0.282463 seconds.

โปรดลิงกับมันจนกว่าคุณจะพอใจ

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <random>
#include <chrono>
#include <algorithm>

class Timer {
public:
  Timer() { reset(); }
  void reset() { start = now(); }

  double getElapsedSeconds() {
    std::chrono::duration<double> seconds = now() - start;
    return seconds.count();
  }

private:
  static std::chrono::time_point<std::chrono::high_resolution_clock> now() {
    return std::chrono::high_resolution_clock::now();
  }

  std::chrono::time_point<std::chrono::high_resolution_clock> start;

};

struct ST {
  int X;
  int Y;
  double Cost;
  std::string Label;

  bool operator==(const ST &rhs) {
    return
      (X == rhs.X) &&
      (Y == rhs.Y) &&
      (Cost == rhs.Cost) &&
      (Label == rhs.Label);
  }

  bool operator<(const ST &rhs) {
    if(X > rhs.X) { return false; }
    if(Y > rhs.Y) { return false; }
    if(Cost > rhs.Cost) { return false; }
    if(Label >= rhs.Label) { return false; }
    return true;
  }
};

using TP = std::tuple<int, int, double, std::string>;

std::pair<std::vector<ST>, std::vector<TP>> generate() {
  std::mt19937 mt(std::random_device{}());
  std::uniform_int_distribution<int> dist;

  constexpr size_t SZ = 1000000;

  std::pair<std::vector<ST>, std::vector<TP>> p;
  auto& s = p.first;
  auto& d = p.second;
  s.reserve(SZ);
  d.reserve(SZ);

  for(size_t i = 0; i < SZ; i++) {
    s.emplace_back();
    auto& sb = s.back();
    sb.X = dist(mt);
    sb.Y = dist(mt);
    sb.Cost = sb.X * sb.Y;
    sb.Label = std::to_string(sb.Cost);

    d.emplace_back(std::tie(sb.X, sb.Y, sb.Cost, sb.Label));
  }

  return p;
}

int main() {
  Timer timer;

  auto p = generate();
  auto& structs = p.first;
  auto& tuples = p.second;

  timer.reset();
  std::sort(structs.begin(), structs.end());
  double stSecs = timer.getElapsedSeconds();

  timer.reset();
  std::sort(tuples.begin(), tuples.end());
  double tpSecs = timer.getElapsedSeconds();

  std::cout << "Structs took " << stSecs << " seconds.\nTuples took " << tpSecs << " seconds.\n";

  std::cin.get();
}

ขอบคุณสำหรับสิ่งนี้. ผมสังเกตเห็นว่าเมื่อการเพิ่มประสิทธิภาพด้วย-O3, ใช้เวลาน้อยกว่าtuples structs
Simog

3

โครงสร้างของ POD มักจะใช้ (ab) ในการอ่านและการทำให้เป็นอนุกรมที่ต่อเนื่องกันในระดับต่ำ ทูเพิลอาจได้รับการปรับให้เหมาะสมยิ่งขึ้นในบางสถานการณ์และรองรับฟังก์ชันอื่น ๆ ดังที่คุณกล่าว

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


3

เท่าที่ "ฟังก์ชั่นทั่วไป" ไป Boost.Fusion สมควรได้รับความรัก ... และโดยเฉพาะอย่างยิ่งBOOST_FUSION_ADAPT_STRUCT

ริปจากเพจ: ABRACADBRA

namespace demo
{
    struct employee
    {
        std::string name;
        int age;
    };
}

// demo::employee is now a Fusion sequence
BOOST_FUSION_ADAPT_STRUCT(
    demo::employee
    (std::string, name)
    (int, age))

ซึ่งหมายความว่าอัลกอริทึมฟิวชั่นทั้งหมดสามารถใช้ได้กับโครงสร้างdemo::employeeแล้ว


แก้ไข : เกี่ยวกับความแตกต่างของประสิทธิภาพหรือความเข้ากันได้tupleของเลย์เอาต์เค้าโครงของการใช้งานถูกกำหนดไว้ดังนั้นจึงไม่สามารถใช้ร่วมกันได้ (ดังนั้นคุณจึงไม่ควรใช้ระหว่างการเป็นตัวแทนอย่างใดอย่างหนึ่ง) และโดยทั่วไปฉันคาดหวังว่าจะไม่มีประสิทธิภาพที่แตกต่างกัน (อย่างน้อยในรุ่น) ต้องขอบคุณ การฝังตัวของget<N>.


16
ฉันไม่เชื่อว่านี่คือคำตอบที่ได้รับการโหวตสูงสุด มันไม่ตอบคำถามด้วยซ้ำ คำถามเกี่ยวกับtuples and structs ไม่เพิ่ม!
gsamaras

@ G.Samaras: คำถามเกี่ยวกับความแตกต่างระหว่างทูเปิลและstructและโดยเฉพาะอย่างยิ่งอัลกอริทึมที่มีอยู่มากมายในการจัดการกับทูเปิลในกรณีที่ไม่มีอัลกอริทึมในการจัดการโครงสร้าง (เริ่มต้นด้วยการทำซ้ำในฟิลด์ของมัน) คำตอบนี้แสดงให้เห็นว่าช่องว่างนี้สามารถเชื่อมโยงได้โดยใช้ Boost.Fusion ซึ่งนำไปสู่structอัลกอริทึมจำนวนมากเท่าที่มีอยู่ในสิ่งที่สอง ฉันได้เพิ่มข้อความแจ้งเล็กน้อยสำหรับคำถามสองข้อที่ถาม
Matthieu M.

3

นอกจากนี้เค้าโครงข้อมูลยังใช้งานร่วมกันได้ (หล่อสลับกัน)?

น่าแปลกที่ฉันไม่เห็นคำตอบโดยตรงสำหรับส่วนนี้ของคำถาม

คำตอบคือไม่มี หรืออย่างน้อยก็ไม่น่าเชื่อถือเนื่องจากเค้าโครงของทูเปิลไม่ได้ระบุไว้

ประการแรก struct ของคุณเป็นมาตรฐานเค้าโครงประเภท การจัดลำดับการเพิ่มช่องว่างและการจัดตำแหน่งของสมาชิกได้รับการกำหนดไว้อย่างดีโดยการผสมผสานระหว่างมาตรฐานและ ABI ของแพลตฟอร์มของคุณ

หากทูเปิลเป็นประเภทเลย์เอาต์มาตรฐานและเรารู้ว่าฟิลด์ถูกจัดวางตามลำดับที่ระบุประเภทเราอาจมีความมั่นใจว่ามันจะตรงกับโครงสร้าง

โดยปกติทูเปิลจะถูกนำไปใช้โดยใช้การถ่ายทอดทางพันธุกรรมโดยหนึ่งในสองวิธี: รูปแบบการออกแบบซ้ำ Loki / Modern C ++ แบบเก่าหรือรูปแบบที่แตกต่างกัน ไม่ใช่ประเภทเค้าโครงมาตรฐานเนื่องจากทั้งสองละเมิดเงื่อนไขต่อไปนี้:

  1. (ก่อน C ++ 14)

    • ไม่มีคลาสพื้นฐานที่มีสมาชิกข้อมูลที่ไม่คงที่หรือ

    • ไม่มีสมาชิกข้อมูลที่ไม่คงที่ในคลาสที่ได้รับมากที่สุดและมากที่สุดหนึ่งคลาสฐานที่มีสมาชิกข้อมูลที่ไม่คงที่

  2. (สำหรับ C ++ 14 ขึ้นไป)

    • มีสมาชิกข้อมูลที่ไม่คงที่และบิตฟิลด์ทั้งหมดที่ประกาศในคลาสเดียวกัน (ทั้งหมดในที่ได้รับหรือทั้งหมดในฐานบางส่วน)

เนื่องจากคลาสฐานแต่ละใบมีองค์ประกอบทูเปิลเดียว (NB. ทูเปิลองค์ประกอบเดียวอาจเป็นประเภทโครงร่างมาตรฐานแม้ว่าจะไม่มีประโยชน์มากนัก) ดังนั้นเราทราบดีว่ามาตรฐานไม่ได้รับประกันว่าทูเพิลมีช่องว่างภายในหรือการจัดตำแหน่งเดียวกันกับโครงสร้าง

นอกจากนี้ควรสังเกตว่าทูเปิลแบบเรียกซ้ำแบบเก่าโดยทั่วไปจะจัดวางสมาชิกข้อมูลในลำดับย้อนกลับ

บางครั้งมันก็ใช้งานได้จริงในทางปฏิบัติสำหรับคอมไพเลอร์และการรวมกันของประเภทฟิลด์บางประเภทในอดีต (ในกรณีหนึ่งคือใช้สิ่งที่ซ้ำกันซ้ำหลังจากย้อนกลับลำดับฟิลด์) ตอนนี้ใช้งานไม่ได้อย่างน่าเชื่อถือแน่นอน (ในคอมไพเลอร์เวอร์ชัน ฯลฯ ) และไม่เคยรับประกันตั้งแต่แรก


1

ไม่ควรมีความแตกต่างด้านประสิทธิภาพ (แม้จะไม่มีนัยสำคัญก็ตาม) อย่างน้อยในกรณีปกติพวกเขาจะส่งผลให้มีเค้าโครงหน่วยความจำเดียวกัน อย่างไรก็ตามการคัดเลือกนักแสดงระหว่างพวกเขาอาจไม่จำเป็นสำหรับการทำงาน (แม้ว่าฉันจะเดาว่ามีโอกาสที่ค่อนข้างจะเป็นไปได้)


4
อันที่จริงฉันคิดว่าอาจมีความแตกต่างเล็กน้อย structต้องจัดสรรเวลาอย่างน้อย 1 ไบต์สำหรับแต่ละ subobject ในขณะที่ผมคิดว่าtupleจะได้รับไปกับการเพิ่มประสิทธิภาพจากวัตถุที่ว่างเปล่า นอกจากนี้ในเรื่องการบรรจุและการจัดตำแหน่งอาจเป็นไปได้ว่าสิ่งมีชีวิตมีความคล่องตัวมากขึ้น
Matthieu M.

1

ประสบการณ์ของฉันคือเมื่อเวลาผ่านไปฟังก์ชันการทำงานเริ่มคืบคลานขึ้นตามประเภท (เช่นโครงสร้าง POD) ซึ่งเคยเป็นผู้ถือข้อมูลที่บริสุทธิ์ สิ่งต่างๆเช่นการปรับเปลี่ยนบางอย่างที่ไม่ควรใช้ความรู้ภายในของข้อมูลการรักษาค่าคงที่เป็นต้น

นั่นเป็นสิ่งที่ดี มันเป็นรากฐานของการวางแนววัตถุ มันเป็นเหตุผลว่าทำไม C กับคลาสจึงถูกคิดค้นขึ้น การใช้คอลเลกชันข้อมูลที่บริสุทธิ์เช่นสิ่งที่เพิ่มขึ้นจะไม่เปิดสำหรับส่วนขยายเชิงตรรกะดังกล่าว โครงสร้างคือ นั่นเป็นเหตุผลที่ฉันมักจะเลือกใช้โครงสร้าง

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

ปัญหาอีกประการหนึ่งคือรหัสความปลอดภัยประเภทและเอกสารในตัวเอง ถ้าฟังก์ชันของคุณได้รับวัตถุประเภทinbound_telegramหรือlocation_3Dชัดเจน หากได้รับunsigned char *หรือtuple<double, double, double>ไม่ได้รับ: โทรเลขอาจเป็นขาออกและทูเปิลอาจเป็นการแปลแทนตำแหน่งหรืออาจเป็นการอ่านอุณหภูมิต่ำสุดจากวันหยุดยาว ใช่คุณสามารถพิมพ์ดีฟเพื่อทำให้ความตั้งใจชัดเจน แต่นั่นไม่ได้ป้องกันไม่ให้อุณหภูมิผ่านไป

ปัญหาเหล่านี้มีแนวโน้มที่จะมีความสำคัญในโครงการที่มีขนาดเกินกำหนด ข้อเสียของ tuples และข้อดีของชั้นเรียนที่ซับซ้อนไม่สามารถมองเห็นได้และเป็นค่าใช้จ่ายในโครงการขนาดเล็ก การเริ่มต้นด้วยชั้นเรียนที่เหมาะสมแม้สำหรับการรวมข้อมูลเพียงเล็กน้อยที่ไม่เด่นก็จ่ายเงินปันผลล่าช้า

แน่นอนว่ากลยุทธ์ที่เป็นไปได้อย่างหนึ่งคือการใช้ผู้ถือข้อมูลที่บริสุทธิ์เป็นผู้ให้บริการข้อมูลพื้นฐานสำหรับตัวห่อคลาสซึ่งให้การดำเนินการกับข้อมูลนั้น


1

ไม่ต้องกังวลเรื่องความเร็วหรือเค้าโครงนั่นคือการเพิ่มประสิทธิภาพนาโนและขึ้นอยู่กับคอมไพเลอร์และไม่มีความแตกต่างเพียงพอที่จะส่งผลต่อการตัดสินใจของคุณ

คุณใช้โครงสร้างสำหรับสิ่งต่าง ๆ ที่อยู่ร่วมกันอย่างมีความหมายเพื่อรวมกันเป็นองค์รวม

คุณใช้ทูเพิลสำหรับสิ่งที่อยู่ด้วยกันโดยบังเอิญ คุณสามารถใช้ทูเปิลได้ตามธรรมชาติในโค้ดของคุณ


1

การตัดสินโดยคำตอบอื่นการพิจารณาประสิทธิภาพจะน้อยที่สุด

ดังนั้นจึงควรคำนึงถึงการใช้งานจริงความสามารถในการอ่านและการบำรุงรักษา และstructโดยทั่วไปจะดีกว่าเนื่องจากสร้างประเภทที่อ่านและเข้าใจได้ง่ายขึ้น

บางครั้งอาจจำเป็นต้องใช้std::tuple(หรือแม้แต่std::pair) เพื่อจัดการกับโค้ดด้วยวิธีทั่วไป ยกตัวอย่างเช่นการดำเนินการบางอย่างที่เกี่ยวข้องกับชุดพารามิเตอร์ variadic std::tupleจะเป็นไปไม่ได้โดยสิ่งที่ต้องการ std::tieเป็นตัวอย่างที่ดีในการstd::tupleปรับปรุงโค้ด (ก่อน C ++ 20)

แต่ทุกที่ที่คุณสามารถใช้ a structได้คุณควรใช้ไฟล์struct. มันจะมอบความหมายเชิงความหมายให้กับองค์ประกอบของประเภทของคุณ นั่นเป็นสิ่งล้ำค่าในการทำความเข้าใจและการใช้ประเภท ในทางกลับกันสิ่งนี้สามารถช่วยหลีกเลี่ยงข้อผิดพลาดโง่ ๆ :

// hard to get wrong; easy to understand
cat.arms = 0;
cat.legs = 4;

// easy to get wrong; hard to understand
std::get<0>(cat) = 0;
std::get<1>(cat) = 4;

0

ฉันรู้ว่ามันเป็นธีมเก่า แต่ตอนนี้ฉันกำลังจะตัดสินใจเกี่ยวกับส่วนหนึ่งของโปรเจ็กต์ของฉันฉันควรไปทางทูเพิลหรือโครงสร้าง - ทาง หลังจากอ่านกระทู้นี้ฉันมีความคิดบางอย่าง

  1. เกี่ยวกับวีทตี้และการทดสอบประสิทธิภาพ: โปรดทราบว่าโดยปกติแล้วคุณสามารถใช้ memcpy, memset และเทคนิคที่คล้ายกันสำหรับโครงสร้างได้ สิ่งนี้จะทำให้ประสิทธิภาพดีกว่าสิ่งที่ดึงดูด

  2. ฉันเห็นข้อดีบางประการในสิ่งที่ได้:

    • คุณสามารถใช้ tuples เพื่อส่งคืนชุดของตัวแปรจากฟังก์ชันหรือวิธีการและลดจำนวนประเภทที่คุณใช้
    • จากข้อเท็จจริงที่ว่าทูเปิลมีตัวดำเนินการ <, ==,> ที่กำหนดไว้ล่วงหน้าคุณยังสามารถใช้ทูเปิลเป็นคีย์ในแผนที่หรือ hash_map ซึ่งคุ้มค่ากว่ามากสำหรับโครงสร้างที่คุณต้องใช้ตัวดำเนินการเหล่านี้

ฉันได้ค้นหาเว็บและในที่สุดก็มาถึงหน้านี้: https://arne-mertz.de/2017/03/smelly-pair-tuple/

โดยทั่วไปฉันเห็นด้วยกับข้อสรุปสุดท้ายจากด้านบน


1
สิ่งนี้ดูเหมือนว่าคุณกำลังทำอะไรอยู่และไม่ใช่คำตอบสำหรับคำถามเฉพาะนั้นหรือ?
Dieter Meemken

ไม่มีสิ่งใดที่ป้องกันไม่ให้คุณใช้ memcpy กับ tuples
Peter - Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.