How can a member know which instance of the class it is built on?


    class C {
    public
      T x;
    };

      

Is there an elegant way for the constructor x to know implicitly in which C instance it creates?


I implemented this behavior with some dirty inelegant mechanisms. I need this for my sqlite3 shell. I don't like all the wrappers I've seen, their APIs are IMO ugly and awkward. I want something like this:

    class TestRecordset: public Recordset {
    public:
      // The order of fields declarations specifies column index of the field.
      // There is TestRecordset* pointer inside Field class, 
      // but it goes here indirectly  so I don't have to 
      // re-type all the fields in the constructor initializer list.
      Field<__int64> field1;
      Field<wstring> field2;
      Field<double> field3;

      // have TestRecordset* pointer too so only name of parameter is specified
      // in TestRecordset constructor
      Param<wstring> param;

      virtual string get_sql() {
        return "SELECT 1, '1', NULL FROM test_table WHERE param=:PARAM";
      }

      // try & unlock are there because of my dirty tricks.
      // I want to get rid of them.

      TestRecordset(wstring param_value)
      try : Recordset(open_database(L"test.db")), param("PARAM") {
        param = param_value;
       // I LOVE RAII but i cant use it here. 
       // Lock is set in Recordset constructor, 
       // not in TestRecordset constructor.
        unlock(this);
        fetch();
      } catch(...) {
        unlock(this);
        throw;
      }
    };

      



I want to clarify the fact - it is a part of the working code. You can do this in C ++. I just want to do it in a more nice way.


I've found a way to get rid of unlock and try block. I've remembered there is such a thing as thread local storage. Now I can write constructor as simple as that:
  TestRecordset (wstring param_value): 
    Recordset (open_database (L "test.db")), param ("PARAM") {
        param = param_value;
        fetch ();
   }


dribeas: My goal is to avoid unnecessary and tedious typing. Without any backstage tricks, I would need to enter for each field and Param:
TestRecordset (wstring param_value): Recordset (open_database (L "test.db")), param (this, "PARAM"),
   field1 (this, 0), field2 (this, 1), field3 (this, 2) {...}

It's redundant, ugly, and inconvenient. For example, if I need to add a new field in the middle of the SELECT, I will have to rewrite all the column numbers. Some notes on your post:

  • Fields and params are initialized by their default constructors.
  • The order of the initializers in the constructor doesn't matter. Fields are always initialized in the order in which they are declared. I used this fact to keep track of the column index for fields
  • The base classes are built first. So when the fields are built, the internal list of fields in the Recordset is ready for use by default Filed.
  • I cannot use RAII here. I need to acquire the lock in the Recorset constructor and release it in the TestRecordset constructor after all the fields are constructed .
0


source to share


5 answers


Not. Objects don't have to know where they are used in order to work. As for x, it is an instance of T. What is it. It doesn't behave differently depending on whether it is a member of class C, a member of class D, automatic, temporary, etc.

Also, even if T's constructor knew about an instance of C, that instance of C would be incomplete, since of course it hasn't finished building yet because its members weren't built. C ++ offers you a lot of chances to shoot yourself in the foot, but offering you a reference to an incomplete object in another class constructor is not one of them.

The only thing I can think of to get closer to your example code is to do something like



#define INIT_FIELDS field1(this), field2(this), field3(this)

      

right after the field list, use INIT_FIELDS in the initializer list and #undef. It's still a duplication, but at least it's all in one place. This will probably surprise your colleagues.

Another way to make sure you haven't forgotten a field is to remove the zero-arg constructor from the field. Again, you still have to type, but at least if you forget something, the compiler will catch it. Not the dry nature of init lists, I think something C ++ just needs to live with.

+8


source


Adding a one-by-one answer, the actual question you should be asking is, "What's wrong with my solution design that it requires objects to know where they were instantiated?"



+2


source


I do not think so.

Out of pure curiosity, why does this matter? do you have a context in which this might be useful?

M.

+1


source


I experiment with things like this all the time in C # - I use reflection to do this.

Consider getting a reflection or code generation library for C ++ to help you do what you want.

Now I can't tell you how to find a good reflection or code generation library for C ++, but that's a different question!

0


source


I am interested in your code. Do you note that all the fields plus the param attribute have pointers back to the TestRecordSet, but they don't need to be initialized? Or is it a question of how to avoid having to pass these pointers during construction?

If you want, don't add all the fields to your constructor's initialization list, this is a misguided goal. You should always initialize all of your members in the initialization list and do so in the same order in which they are declared in the class (this is not a language, but more experience from all over the world).

Your use of the try constructor block is just that it only recommended that you use this functionality (anyone reading GOTW # 66 ) if really required. If a RecordSet has been constructed (and therefore the lock is locked) and something goes wrong after that in the constructor, then [see. Quote below] The RecordSet will be destroyed and if it uses RAII internally it will release the lock, so I believe try / catch is really unnecessary.

C ++ 03, 15.2 Exception Handling / Constructors and Destructors

An object that is partially constructed or partially destroyed will be destroyed by the destructors for all its fully constructed subobjects, i.e. for subobjects for which the constructor has completed execution and the destructor has not yet started execution.

0


source







All Articles