C++ struct has no default memberwise comparison for operator==?!

Started by
30 comments, last by Xentropy 16 years, 10 months ago
Well, if you have huge structures, it's likely that there's a problem with your design.

If you think there isn't you could let them all inherit from a class that has == defined as a raw memory compare (search the standard library) between the structs, and make sure that the structs are tightly packed in memory (so no bytes in their memory block have uninitialized values).

It qualifies as an ugly hack though, and I'd much rather rethink my design than try something like it.
Advertisement
My solutions would be:
  • Refactor your design (using a dynamic property system with std::map<std::string,boost::any> for instance).
  • Write an automatic code generator, if the layout of your structure is straightforward.
  • Write them by hand.
If only it could be that simple. Isn't that kind of database, though. Think of it as a file that contains configuration data. There are 100 different configuration settings, each with various types of values. You want to see if one instance of a configuration file is using all the same settings as another instance of a different configuration file. Is there a method that doesn't involve 100 ifs or 99 &&'s? :)

Looking like no.

Edit: Or maybe yes. The memcpy method looks promising. The only types I'm using that aren't POD are std::string and std::bitset. I'll have to make sure they'll work with memcpy.

Edit2: And it isn't my design, it's a 20-year old BBS door game I'm reading the database files of. I can't really change the way the data is stored in the files. I could change the way I read the files; right now I just read the data directly into a struct of the same size and "shape" as the data in the files. Makes for easy reading of the sort:

CfigInterfaceFile.read(reinterpret_cast<char *>(&configData), sizeof(configDataStruct));


And for anyone that just had IP klaxons sound in their heads, I have the permission of the game's author to do this. :)

Edit3: You know, I pretty much KNOW std::string and std::bitset will work with memcpy since I'm loading strings and bitsets out of the files directly in memcpy-style fashion. I'll give that a shot.

Edit4: Err, I think you meant memcmp. I'm trying that now and will let you know. :) All the types I'm using should be fine with just comparing the bytes in memory directly.

[Edited by - Xentropy on June 8, 2007 5:25:26 AM]
Quote:Original post by Xentropy
If only it could be that simple. Isn't that kind of database, though. Think of it as a file that contains configuration data.


Ahh. I'd definately go for #3 in my edit, then.

(Actually, no, that's a lie -- I'd assume YAGNI!!!)
Can all the data be represented by strings?

Would a big array of strings, sort & compare not work?

Are you using this to see if there have been any configuration changes so you can write the changes out to the file/database/...?
Quote:Original post by MaulingMonkey
Quote:Original post by Xentropy
If only it could be that simple. Isn't that kind of database, though. Think of it as a file that contains configuration data.


Ahh. I'd definately go for #3 in my edit, then.

(Actually, no, that's a lie -- I'd assume YAGNI!!!)


I wish I didn't need it. I hadn't implemented comparison until now specifically BECAUSE I didn't need it. And now I do, and wish needing it didn't mean a ton of code.

What I need to do right now is compare a given game's settings to the stock settings so I can just report the game is stock instead of spelling out all the edits.

I'll look into the boost options you mentioned and see if I can change my library to using one of those instead of structs. Even if it's just as much work, at least it'll be more productive work since it'll mean less work for any future additions/changes.
If you're using text configuration files, you could probably just deal with the original std::string array/map for the most part. Something like:


#include <algorithm>#include <map>#include <string>#include <boost/lexical_cast.hpp>#include <iostream>#include <cassert>class configuration_file {    std::map< std::string /* name */, std::string /* value */ > data;public:    configuration_file( const std::string& filename ) {        std::ifstream file( filename.c_str() );        for ( std::string line; std::getline( file, line ); ) {            std::string::size_type equals = line.find("=");            if ( equals == std::string::npos ) {                // error handling --- not a key = value pairing            }            // Key/Value finding:            std::string key = line.substr( 0, equals );            std::string value = line.substr( equals , std::string::npos );            // Whitespace stripping:            key = key.substr( key.find_first_not_of( " \t" ), key.find_last_not_of( " \t" ) );            value = value.substr( value.find_first_not_of( " \t" ), value.find_last_not_of( " \t" ) );            data[key] = value;        }    }    friend bool operator==( const configuration_file& lhs, const configuration_file& rhs ) {        if ( lhs.data.size() != rhs.data.size() ) return false;        return std::equal( lhs.data.begin(), lhs.data.end(), rhs.data.begin() );    }    friend bool operator!=( const configuration_file& lhs, const configuration_file& rhs ) {        return !(lhs==rhs);    }    template < typename T >    T get( const std::string& fieldname ) const {        return boost::lexical_cast<T>( data[fieldname] );    }    template < typename T >    void set( const std::string& fieldname, T value ) {        data[fieldname] = boost::lexical_cast< std::string >( value );    }};void example() {    configuration_file default_cfg( "default.cfg" );    configuration_file config( "configuration.cfg" );    if ( default_cfg != config ) {        std::cout << "Stop tamperin' with mah files!" << std::endl;    } else {        configuration.set<int>( "pineapple", 42 ); //he he tampering    }    assert( default_cfg.get<int>( "resolution_width" ) <= 800 );    assert( default_cfg.get<int>( "resolution_height" ) <= 600 );}
Quote:Original post by MaulingMonkey
If you're using text configuration files


They're binary. Some fields are strings but it's stored in binary. And reverse-engineering the field locations and meanings was pretty fun. ;) Of course, the author sent me the source so I could verify everything later, but... The game was written in Pascal, too, so converting the Pascal-strings to C-strings and converting real48's to 64-bit doubles were additional challenges I already worked out long ago.
Quote:Original post by Xentropy
Quote:Original post by MaulingMonkey
If you're using text configuration files


They're binary. Some fields are strings but it's stored in binary. And reverse-engineering the field locations and meanings was pretty fun. ;) Of course, the author sent me the source so I could verify everything later, but... The game was written in Pascal, too, so converting the Pascal-strings to C-strings and converting real48's to 64-bit doubles were additional challenges I already worked out long ago.


Hmm. Well, it looks like boost::variant already defines operator== if all it's types are EqualityComparable, so it'd be a very easy adoption. Just switching the load code, the get/set to boost::get<T>(data) and data=value; instead of lexical casting, and the various use of std::string as a value representation to a std::variant<std::string,double,...>, and I think that's it.
Thanks to joanusdmentia for pointing me in the direction of direct memory comparison. Preliminary testing seems to indicate it works!

bool operator==(const configDataStruct &rhs) {	if (memcmp(this,&rhs,sizeof(*this)))		return false;	else return true;}


I know it may not be the most portable thing in the world, but if the portability of this breaks, so will the portability of the way I'm reading the files in the first place.

Edit: And I'm still going to look into using boost::variant to improve portability. Big thanks to MaulingMonkey as well. :) And everyone else that contributed to this thread. Saves me a TON of work in the long run!

This topic is closed to new replies.

Advertisement