C ++ में जेनेरिक स्ट्रक्चर्स की तुलना कैसे करें?


13

मैं एक सामान्य तरीके से संरचनाओं की तुलना करना चाहता हूं और मैंने कुछ ऐसा किया है (मैं वास्तविक स्रोत को साझा नहीं कर सकता हूं, इसलिए यदि आवश्यक हो तो अधिक विवरण मांगें):

template<typename Data>
bool structCmp(Data data1, Data data2)
{
  void* dataStart1 = (std::uint8_t*)&data1;
  void* dataStart2 = (std::uint8_t*)&data2;
  return memcmp(dataStart1, dataStart2, sizeof(Data)) == 0;
}

यह ज्यादातर इरादा के अनुसार काम करता है, कभी-कभी यह झूठे को छोड़कर भी वापस आता है, भले ही दो संरचनात्मक उदाहरणों में समान सदस्य हों (मैंने ग्रहण डिबगर के साथ जांच की है)। कुछ खोज के बाद मुझे पता चला कि memcmpगद्देदार इस्तेमाल होने वाली संरचना के कारण विफल हो सकते हैं।

क्या मेमोरी की तुलना करने का एक अधिक उचित तरीका है जो पैडिंग के प्रति उदासीन है? मैं उपयोग की जाने वाली संरचनाओं को संशोधित करने में सक्षम नहीं हूं (वे एक एपीआई का उपयोग कर रहे हैं) और कई अलग-अलग संरचनाओं का उपयोग करने वाले कुछ अलग-अलग सदस्य हैं और इस तरह व्यक्तिगत रूप से सामान्य तरीके से (मेरी जानकारी के लिए) तुलना नहीं की जा सकती है।

संपादित करें: मैं दुर्भाग्य से C ++ 11 के साथ फंस गया हूं। यह उल्लेख किया जाना चाहिए पहले ...


क्या आप एक उदाहरण दिखा सकते हैं जहाँ यह विफल हो जाता है? पैडिंग एक ही प्रकार के सभी उदाहरणों के लिए समान होनी चाहिए, नहीं?
आइडलक 463035818

1
@ idclev463035818 पैडिंग अनिर्दिष्ट है, आप इसे मान नहीं सकते हैं और मेरा मानना ​​है कि इसे पढ़ने की कोशिश करने के लिए यूबी है (उस अंतिम भाग पर यकीन नहीं)।
फ्रैंकोइस एंड्रीक्स

@ idclev463035818 पैडिंग मेमोरी में समान सापेक्ष स्थानों पर है, लेकिन इसमें अलग-अलग डेटा हो सकते हैं। यह संरचना के सामान्य उपयोगों में छोड़ दिया जाता है, इसलिए कंपाइलर इसे शून्य करने के लिए परेशान नहीं कर सकता है।
NO_NAME

2
@ idclev463035818 गद्दी का आकार समान है। बिट्स की स्थिति जो उस पैडिंग का गठन करती है, वह कुछ भी हो सकती है। जब आप memcmpउन पैडिंग बिट्स को अपनी तुलना में शामिल करते हैं।
फ्रांस्वा एंड्रीक्स

1
मैं Yksisarvinen के साथ सहमत हूं ... कक्षाओं का उपयोग करें, न कि संरचनाएं, और ==ऑपरेटर को लागू करें । का उपयोग करना memcmpअविश्वसनीय है, और जल्द ही या बाद में आप कुछ वर्ग के साथ व्यवहार करेंगे जो कि "इसे दूसरों से थोड़ा अलग करना है।" यह एक ऑपरेटर में लागू करने के लिए बहुत साफ और कुशल है। वास्तविक व्यवहार बहुरूपी होगा लेकिन स्रोत कोड साफ होगा ... और, स्पष्ट है।
माइक रॉबिन्सन

जवाबों:


7

नहीं, memcmpऐसा करने के लिए उपयुक्त नहीं है। और C ++ में प्रतिबिंब इस बिंदु पर ऐसा करने के लिए अपर्याप्त है (ऐसे प्रायोगिक संकलक होने जा रहे हैं जो प्रतिबिंब का समर्थन करते हैं जो पहले से ही ऐसा करने के लिए पर्याप्त मजबूत है, और आपके लिए आवश्यक विशेषताएं हो सकती हैं)।

अंतर्निहित प्रतिबिंब के बिना, आपकी समस्या को हल करने का सबसे आसान तरीका कुछ मैनुअल प्रतिबिंब करना है।

इसे लो:

struct some_struct {
  int x;
  double d1, d2;
  char c;
};

हम कम से कम काम करना चाहते हैं ताकि हम इनमें से दो की तुलना कर सकें।

अगर हमारे पास है:

auto as_tie(some_struct const& s){ 
  return std::tie( s.x, s.d1, s.d2, s.c );
}

या

auto as_tie(some_struct const& s)
-> decltype(std::tie( s.x, s.d1, s.d2, s.c ))
{
  return std::tie( s.x, s.d1, s.d2, s.c );
}

के लिए है, तो:

template<class S>
bool are_equal( S const& lhs, S const& rhs ) {
  return as_tie(lhs) == as_tie(rhs);
}

बहुत अच्छा काम करता है।

हम इस प्रक्रिया को थोड़ा काम के साथ पुनरावर्ती होने के लिए विस्तारित कर सकते हैं; संबंधों की तुलना करने के बजाय, टेम्पलेट में लिपटे प्रत्येक तत्व की तुलना करें, और उस टेम्पलेट का operator==पुनरावर्ती इस नियम को लागू करता है ( as_tieतुलना करने के लिए तत्व को लपेटता है ) जब तक कि तत्व में पहले से ही काम नहीं होता है ==, और सरणियों को संभालता है।

इसके लिए थोड़ी सी लाइब्रेरी (कोड की 100ish लाइनें) की आवश्यकता होगी, साथ में प्रति मैनुअल "प्रतिबिंब" डेटा का एक सा लिखने के साथ। यदि आपके पास मौजूद संरचनाओं की संख्या सीमित है, तो मैन्युअल रूप से प्रति-संरचना कोड लिखना आसान हो सकता है।


शायद पाने के तरीके हैं

REFLECT( some_struct, x, d1, d2, c )

as_tieभयानक मैक्रोज़ का उपयोग करके संरचना उत्पन्न करना । लेकिन as_tieकाफी सरल है। में पुनरावृत्ति कष्टप्रद है, यह उपयोगी है:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

इस स्थिति में और कई अन्य। के साथ RETURNS, लेखन as_tieहै:

auto as_tie(some_struct const& s)
  RETURNS( std::tie( s.x, s.d1, s.d2, s.c ) )

पुनरावृत्ति को दूर करना।


यहाँ यह पुनरावर्ती बनाने में एक छुरा है:

template<class T,
  typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::tie(t))

template<class...Ts,
  typename std::enable_if< (sizeof...(Ts) > 1), bool>::type = true
>
auto refl_tie( Ts const&... ts )
  RETURNS(std::make_tuple(refl_tie(ts)...))

template<class T, std::size_t N>
auto refl_tie( T const(&t)[N] ) {
  // lots of work in C++11 to support this case, todo.
  // in C++17 I could just make a tie of each of the N elements of the array?

  // in C++11 I might write a custom struct that supports an array
  // reference/pointer of fixed size and implements =, ==, !=, <, etc.
}

struct foo {
  int x;
};
struct bar {
  foo f1, f2;
};
auto refl_tie( foo const& s )
  RETURNS( refl_tie( s.x ) )
auto refl_tie( bar const& s )
  RETURNS( refl_tie( s.f1, s.f2 ) )

refl_tie (सरणी) (पूरी तरह से पुनरावर्ती, यहां तक ​​कि सरणियों-सरणियों का समर्थन करता है):

template<class T, std::size_t N, std::size_t...Is>
auto array_refl( T const(&t)[N], std::index_sequence<Is...> )
  RETURNS( std::array<decltype( refl_tie(t[0]) ), N>{ refl_tie( t[Is] )... } )

template<class T, std::size_t N>
auto refl_tie( T(&t)[N] )
  RETURNS( array_refl( t, std::make_index_sequence<N>{} ) )

जीवंत उदाहरण

यहां मैं एक std::arrayका उपयोग करता हूं refl_tie। यह संकलन समय पर मेरे पिछले टपल ऑफ रिफ्ल_टी से बहुत तेज है।

भी

template<class T,
  typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::cref(t))

कम्पाइल-टाइम ओवरहेड पर बचाने के std::crefबजाय यहां उपयोग करना std::tie, जैसा crefकि एक बहुत सरल वर्ग है tuple

अंत में, आपको जोड़ना चाहिए

template<class T, std::size_t N, class...Ts>
auto refl_tie( T(&t)[N], Ts&&... ) = delete;

जो एरे सदस्यों को पॉइंटर्स को क्षय करने और पॉइंटर-इक्वैलिटी पर वापस गिरने से रोकेगा (जो आप एरे से नहीं चाहते हैं)।

इसके बिना, यदि आप किसी गैर-प्रतिबिंबित संरचना में एक सरणी पास करते हैं, तो यह सूचक से गैर-प्रतिबिंबित संरचना पर वापस गिरता है refl_tie, जो काम करता है और बकवास करता है।

इसके साथ, आप एक संकलन-समय त्रुटि के साथ समाप्त होते हैं।


पुस्तकालय प्रकारों के माध्यम से पुनरावृत्ति के लिए समर्थन मुश्किल है। आप std::tieउन्हें कर सकते हैं:

template<class T, class A>
auto refl_tie( std::vector<T, A> const& v )
  RETURNS( std::tie(v) )

लेकिन इसके माध्यम से पुनरावृत्ति का समर्थन नहीं करता है।


मैं मैनुअल प्रतिबिंब के साथ इस प्रकार के समाधान को आगे बढ़ाना चाहता हूं। आपके द्वारा प्रदान किया गया कोड C ++ 11 के साथ काम नहीं करता है। किसी भी मौका तुम मुझे उस के साथ मदद कर सकते हैं?
फ्रेड्रिक एनटॉर्प

1
C ++ 11 में काम नहीं करने का कारण रिटर्न रिटर्न टाइपिंग की कमी है as_tie। C ++ 14 से शुरू होकर यह स्वचालित रूप से कट जाता है। आप auto as_tie (some_struct const & s) -> decltype(std::tie(s.x, s.d1, s.d2, s.c));C ++ 11 में उपयोग कर सकते हैं । या वापसी प्रकार स्पष्ट रूप से बताएं।
दारुकुक

1
@FredrikEnetorp फिक्स्ड, प्लस एक मैक्रो जो इसे लिखना आसान बनाता है। इसे पूरी तरह से पुनरावर्ती कार्य के लिए प्राप्त करने का कार्य (इसलिए एक संरचना-की-संरचना, जहां उपग्रहों का as_tieसमर्थन है, स्वचालित रूप से काम करता है) और सहायक सरणी सदस्य विस्तृत नहीं हैं, लेकिन यह संभव है।
यक - एडम नेवरामोंट

धन्यवाद। मैंने भयानक मैक्रों को थोड़ा अलग तरीके से किया, लेकिन कार्यात्मक रूप से समकक्ष। बस एक और समस्या है। मैं एक अलग हेडर फ़ाइल में तुलना को सामान्य बनाने की कोशिश कर रहा हूं और इसे विभिन्न gmock परीक्षण फ़ाइलों में शामिल करता हूं। यह त्रुटि संदेश देता है: `as_tie (Test1 const &) 'की कई परिभाषा मैं उन्हें इनलाइन करने की कोशिश कर रहा हूं, लेकिन यह काम करने के लिए नहीं मिल सकता है।
फ्रेड्रिक एनटॉर्प

1
@FredrikEnetorp inlineकीवर्ड को कई परिभाषा त्रुटियों को दूर करना चाहिए। [सवाल पूछने] बटन का प्रयोग करने के बाद आप एक पाने के लिए कम से कम प्रतिलिपि प्रस्तुत करने योग्य उदाहरण
एडम Nevraumont - Yakk

7

आप सही कह रहे हैं कि इस तरह से मनमाने प्रकार की तुलना करने पर पैडिंग आपके काम आती है।

ऐसे उपाय हैं जो आप कर सकते हैं:

  • यदि आप नियंत्रण में हैं Dataतो उदाहरण के लिए gcc है __attribute__((packed))। इसका प्रदर्शन पर असर पड़ता है, लेकिन यह कोशिश करने लायक हो सकता है। हालांकि, मुझे यह स्वीकार करना होगा कि मैं नहीं जानता कि क्या packedआप पूरी तरह से पैडिंग को बाधित करने में सक्षम हैं। Gcc डॉक्टर कहते हैं:

यह विशेषता, संरचना या संघ प्रकार की परिभाषा से जुड़ी होती है, यह निर्दिष्ट करती है कि संरचना या संघ के प्रत्येक सदस्य को आवश्यक मेमोरी को कम करने के लिए रखा गया है। जब एक एनम परिभाषा से जुड़ा होता है, तो यह इंगित करता है कि सबसे छोटे अभिन्न प्रकार का उपयोग किया जाना चाहिए।

  • यदि आप नियंत्रण में नहीं हैं Dataतो कम से कम std::has_unique_object_representations<T>आपको बता सकता है कि क्या आपकी तुलना सही परिणाम देगी:

यदि T त्रिविमिय रूप से प्राप्य है और यदि किसी मान के साथ टाइप T की दो वस्तुओं का समान ऑब्जेक्ट प्रतिनिधित्व है, तो सदस्य को निरंतर मान समान सत्य प्रदान करता है। किसी अन्य प्रकार के लिए, मूल्य गलत है।

और आगे:

इस विशेषता को यह निर्धारित करने के लिए शुरू किया गया था कि क्या एक प्रकार को बाइट सरणी के रूप में इसके वस्तु प्रतिनिधित्व को हैशिंग द्वारा सही ढंग से हैश किया जा सकता है।

पुनश्च: मैंने केवल पैडिंग को संबोधित किया है, लेकिन यह मत भूलो कि स्मृति में विभिन्न प्रतिनिधित्व के साथ उदाहरणों के लिए समान तुलना की जा सकती है जो किसी भी तरह से दुर्लभ नहीं हैं (जैसे std::string, std::vectorऔर कई अन्य)।


1
मुझे यह उत्तर पसंद है। इस प्रकार के गुण के साथ, आप SFINAE का उपयोग memcmpबिना किसी पैडिंग वाले स्ट्रक्चर्स पर उपयोग करने के लिए कर सकते हैं और operator==केवल तभी लागू कर सकते हैं जब जरूरत हो।
यक्षिसारिवेन

ठीक है धन्यवाद। इससे मैं सुरक्षित रूप से यह निष्कर्ष निकाल सकता हूं कि मुझे कुछ मैनुअल प्रतिबिंबों को करने की आवश्यकता है।
फ्रेड्रिक एनटॉर्प

6

संक्षेप में: सामान्य तरीके से संभव नहीं है।

इसके साथ समस्या memcmpयह है कि पैडिंग में मनमाना डेटा हो सकता है और इसलिए यह memcmpविफल हो सकता है। यदि यह पता लगाने का कोई तरीका था कि पेडिंग कहाँ है, तो आप उन बिट्स को शून्य-आउट कर सकते हैं और फिर डेटा अभ्यावेदन की तुलना कर सकते हैं, जो कि समानता के लिए जाँच करेगा कि क्या सदस्य तुच्छ रूप से तुलनीय हैं (जो कि मामला नहीं है) std::stringक्योंकि दो तारों के बाद से अलग-अलग बिंदु होते हैं, लेकिन इंगित किए गए दो चार-सरणियाँ समान हैं)। लेकिन मुझे पता है कि स्ट्रक्चर्स के पैडिंग में आने का कोई रास्ता नहीं है। आप संरचना को पैक करने के लिए अपने संकलक को बताने की कोशिश कर सकते हैं, लेकिन यह पहुंच को धीमा कर देगा और काम करने के लिए वास्तव में महत्वपूर्ण नहीं है।

इसे लागू करने का सबसे साफ तरीका सभी सदस्यों की तुलना करना है। बेशक यह वास्तव में एक सामान्य तरीके से संभव नहीं है (जब तक कि हम C ++ 23 या उसके बाद के संकलित समय प्रतिबिंब और मेटा क्लासेस नहीं मिलते)। C ++ 20 आगे से, एक डिफ़ॉल्ट उत्पन्न कर सकता है, operator<=>लेकिन मुझे लगता है कि यह केवल एक सदस्य फ़ंक्शन के रूप में भी संभव होगा, फिर से यह वास्तव में लागू नहीं है। यदि आप भाग्यशाली हैं और आपकी तुलना में सभी संरचनाएं operator==परिभाषित हैं, तो आप निश्चित रूप से इसका उपयोग कर सकते हैं। लेकिन इसकी गारंटी नहीं है।

संपादित करें: ठीक है, वास्तव में समुच्चय के लिए एक पूरी तरह से hacky और कुछ हद तक सामान्य तरीका है। (मैंने केवल टुपल्स में रूपांतरण लिखा है, जिनके पास डिफ़ॉल्ट तुलना ऑपरेटर है)। godbolt


अच्छा हैक! दुर्भाग्य से, मैं C ++ 11 के साथ फंस गया हूं इसलिए मैं इसका उपयोग नहीं कर सकता।
फ्रेड्रिक एनटॉर्प

2

C ++ 20 डिफ़ॉल्ट कोमपैरिसन का समर्थन करता है

#include <iostream>
#include <compare>

struct XYZ
{
    int x;
    char y;
    long z;

    auto operator<=>(const XYZ&) const = default;
};

int main()
{
    XYZ obj1 = {4,5,6};
    XYZ obj2 = {4,5,6};

    if (obj1 == obj2)
    {
        std::cout << "objects are identical\n";
    }
    else
    {
        std::cout << "objects are not identical\n";
    }
    return 0;
}

1
हालांकि यह एक बहुत ही उपयोगी विशेषता है, यह पूछे गए सवाल का जवाब नहीं देता है। ओपी ने कहा कि "मैं उपयोग की गई संरचनाओं को संशोधित करने में सक्षम नहीं हूं", जिसका अर्थ है कि, भले ही C ++ 20 डिफ़ॉल्ट समानता ऑपरेटर उपलब्ध थे, ओपी उन्हें उपयोग करने में असमर्थ होगा क्योंकि ==या <=>ऑपरेटर केवल डिफ़ॉल्ट रूप से किया जा सकता है कक्षा के दायरे में।
निकोल बोलस

जैसे निकोल बोलस ने कहा, मैं संरचनाओं को संशोधित नहीं कर सकता।
फ्रेड्रिक एनटॉर्प

1

POD डेटा की मानें, तो डिफ़ॉल्ट असाइनमेंट ऑपरेटर केवल सदस्य बाइट्स को कॉपी करता है। (वास्तव में इसके बारे में 100% निश्चित नहीं है, इसके लिए मेरा शब्द न लें)

आप इसे अपने लाभ के लिए उपयोग कर सकते हैं:

template<typename Data>
bool structCmp(Data data1, Data data2) // Data is POD
{
  Data tmp;
  memcpy(&tmp, &data1, sizeof(Data)); // copy data1 including padding
  tmp = data2;                        // copy data2 only members
  return memcmp(&tmp, &data1, sizeof(Data)) == 0; 
}

@ मेवात आप सही हैं जो एक भयानक जवाब था। एक को फिर से लिखो।
कोस्टा

क्या मानक गारंटी देता है कि असाइनमेंट पैडिंग बाइट्स से अछूता है? मौलिक प्रकारों में समान मूल्य के लिए कई ऑब्जेक्ट प्रतिनिधित्व के बारे में अभी भी चिंता है।
अखरोट

@ मुझे लगता है कि यह करता है
कोस्टा

1
उस लिंक में शीर्ष उत्तर के तहत टिप्पणी से संकेत मिलता है कि यह नहीं है। इस सवाल का जवाब खुद ही कहा गया है कि गद्दी की जरूरत नहीं कॉपी किया है, लेकिन नहीं है कि यह musn't । मैं निश्चित रूप से, हालांकि के लिए नहीं जानता।
अखरोट

मैंने अब इसका परीक्षण किया है और यह काम नहीं करता है। असाइनमेंट पैडिंग बाइट्स को अछूता नहीं छोड़ता है।
फ्रेड्रिक एनटॉर्प

0

मेरा मानना ​​है कि आप magic_getलाइब्रेरी में एंटनी पोलुखिन के शानदार कुटिल वूडू पर एक समाधान को आधार बनाने में सक्षम हो सकते हैं - संरचनाओं के लिए, जटिल कक्षाओं के लिए नहीं।

उस लाइब्रेरी के साथ, हम एक संरचना के विभिन्न क्षेत्रों को उनके उपयुक्त प्रकार के साथ, विशुद्ध रूप से सामान्य-टेम्पर्ड कोड में पुन: व्यवस्थित करने में सक्षम हैं। एंटनी ने इसका उपयोग किया है, उदाहरण के लिए, पूरी तरह से उदारतापूर्वक, सही प्रकार के साथ आउटपुट स्ट्रीम में मनमाने ढंग से स्ट्रीम करने में सक्षम होने के लिए। इसका कारण यह है कि तुलना इस दृष्टिकोण का एक संभावित अनुप्रयोग भी हो सकता है।

... लेकिन आपको C ++ 14 की आवश्यकता होगी। कम से कम यह C ++ 17 से बेहतर है और बाद में अन्य उत्तरों में सुझाव :-P

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.