ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinReaders Letters

Overload Journal #4 - Feb 1994 + Letters to the Editor   Author: Mike Toms

Hi Mike,

I have a number of questions about the ARM.

  1. How is a data type (as oppose to an object of such a type)  be passed  as  a  function  parameter?  (e.g. to implement functions like va_start, va_arg, va_end for variable number of parameters)

    C+ + allows variable numbers of parameters even though there is no guaranteed way of accessing the parameters (like passing C structs or pointers to structs). Bjarnes advice (on pl47 of the ARM) is DON'T USE IT!!. If you are trying to implement them yourself they are so machine and compiler specific and involves compiler magic that it virtually impossible.

  2. If F () is an inline function, do F or &F mean the address of the function? If so, what is the address of an inline function mean?

    Inline is only a hint to the compiler for optimisation purposes that an inline substitution is preferred. There is no guarentee that it will actually be placed inline. Any number of factors could cause the function to be 'not optimised', the size of the function, whether it calls other functions (maybe inline themselves). Taking the address of the function would probably be enough for the optimiser not to inline the function. If you use inline functions taking the address can give you surprises.

  3. Is a virtual inline function really inline?

    The virtual mechanism is usually implemented as indirect function calls - I would think that a virtual inline function is not inlined by the optimiser.
  4. int x = 14; 
    void main(void)
    {
    int x = ::x + 1;  // use :: to access the global x
    {
    int x=    ;//?????
    }
    }
    The initialisation for first local variable x to the value of the global x is done by using the : : operator. Is there is a similar way to initialise the second local variable x to the value of the first local x?

    Not that I know. Try using a different name or adding a reference to the outer x. i.e. int& rx = x;

  5. class A { ..... };
    class B : public A { ..... };
    B b[10];
    A * pA;
    void func(void)
    {
    pA = b;
    pA++;    // ???
    pA =  pA + 3; // ???
    }
    Are the last two statement defined or valid? What is sizeof(*pA)?

    The first (pA++) adds sizeof (A) bytes to the address contained in pA, the second (pA + 3) adds 3*sizeof(A) bytes to the address in pA. If either of these were fortunate enough to be correct addresses of the corresponding B items you would be lucky (and B would contain no data items of its own). Try the following example
    #include <iostream.h>

    class A
    {
    protected:
    int val;
    public:
    virtual void display()
    {
    cout << "Inside A" << endl;
    }
    };
    class B : public A
    {
    protected:
    public:
    void set(int a) { val = a; }
    virtual void display()
    {
    cout << "Inside B["
    << val
    << "]"
    << endl;
    }
    };

    void main (void)
    {
    B b[10];
    {
    int i ;
    for (i = 0; i < 10; i++)
    b[i].set(i);
    }
    A* pA;

    pA = b; // points to b[0]
    pA->display(); // prove it
    pA++;
    pA->display () ; // prove it
    pA = pA + 3;
    pA->display(); // prove it
    }

    As B contains no data of its own, the output will be:

    Inside  B[0]
    Inside  B[l]
    Inside  B[4]

    If a single data item (int x;) is added to B's protected section, the output changes. On the compilers I tested it with, both printed the first line OK, and then hung the PC.

  6. In order to manipulate bit maps (images) or for other reasons, it is desirable to enter some constant in binary format. Apparently C and C++ do not seem to support binary constants directly. I think by using bit-fields constants can be converted to avoid runtime overhead and / or manually converting to Hex format.

    struct binary{
    int bit0:l,  bitl:l,  bit2:l,  bit3:l,
    bit4:l,  bit5:l,  bit6:l,  bit7:l,
    bit8:l,  bit9:l,  bitA:l,  bitB:l,
    bitC:l,  bitD:l,  bitE:l,  bitF:1;
    };
    union BinToHex{
    binary B;
    int    I;
    };
    BinToHex B2H = {{0, 1, 0, 1,
    0, 1, 0, 1,
    1, 0, 1, 0,
    1, 0, 1, 0}};

    The questions is that it looks like this method is compiler implementation dependent and hence not portable, because of the size of int can vary. Is there a better way to do it:

    No! Bitstrings and bitset are already in the draft standard and a proposal on the table for binary literals.

  7. I came across some data types like size_t, time_t, etc., in some header files. Is it true to say that they are for portability? Should we use them instead of int, char, etc. all the time?

    Yes. Also ptrdiff_t and clock_t etc.

  8. Can constructors and destructors be recursive functions? If they can, would you give me an example?

    Yes, but they are not very useful.

    class X 
    {
    public:
    X(int n)
    {
    if (n>0) {
    *this =  X(n-l);
    }
    else
    {
    i = 0;
    }
    }
    private:
    int i;
    };
  9. On page 41 in CVu July 1993, near the middle of the right hand column,

    CVuString AString() ;

    Can this line also mean a function prototype? I suppose the compiler has to look at the context to decide.

    I don't have a copy handy - it looks like a function prototype to me!

  10. Can a constructor call other member function that access the object's data members? I think before the constructor terminates, the object is considered not fully constructed (initialised). So even if this is possible to call a member function, there is a risk.

    Its perfectly legal and there are risks. These risks are the same for any uninitialised data items. Another danger is that if polymorphic member function is called, only the base member function will be called.

  11. If a class x has members new() and delete() (new(size_t) and delete (void  *, size_t)  ?) to maintain free store ( e.g. in order to improve efficiency), delete() collects the freed memory in a pool instead of return to the system, how does the memory release back to the system before the termination of the program, or on request?

    Delete does not have size_t.

    Unless you free (or delete) the memory it does not get returned to the OS until the program terminates.

  12. How is the address of a label (for goto) be obtain in C++? Hence an array of code addresses can be set up for computed jumps. Although I understand the resulting code may be difficult to read and / or maintain (just like using goto), under some situations (e.g. to optimised code), it is desirable to have this ability.

    You cannot take the address of a label. If I find you using goto, you are fired!!!!

  13. On page 248 of "The C++ programming language" 2nd edition by Bjarne Stroustrup,

    class string {
    :
    :
    char & operator[](int i);
    const char & operator[](int i) const;
    :
    :
    };

    these two member functions seem to the same signature. Why are they allowed?

    If you have a const string, the second one will be used; otherwise the first one will be used.

  14. What is the reason for C compilers to put underscore prefixes to every identifier? And is this an ANSI standard?

    No it isn't a standard. I dont know why - anybody?

  15. On page 175 in section 5.5.5 Array of Class objects in the book "The C++ programming language" 2nd edition by Bjarne Stroustrup, there says "to declare an array of objects of a class with constructor, that class must have a default constructor ..."

    I tried the following on Borland C++ 3.1 and it worked. Is this a language extension in BC++ 3.1?

    #include  <iostream.h> 
    class E{
    int x;
    public:
    E(int z) : x(z) {}
    int getx(void) {return x;}
    };
    E e[5] = {0, 1, 2, 3, 4};
    void main(void)
    {
    for (int loop = sizeof(e)/sizeof(E); loop > 0; --loop)
    {
    cout << e[loop-1].getx() << '\n';
    }
    }

    Not sure. But it seems to be a common extension.

  16. a) On page 277 of    "The C++ programming language"  2nd edition by Bjarne Stroustrup, half way down, What is the use of the typedef statement in class Comparator?

    template  <class  T>  class  Comparator   { 
    public:
    typedef T T;
    static int lessthan(T & a, T & b){
    return a < b;
    }
    };

    It looks like the line is trying to (re)define T as T, is this redundant?

    No! It ensures that the comparator class has a member called T which is important if you are using the comparator class as a template argument.

    b) At the beginning of the same page,

    template <class Comp> class Sort{ 
    public:
    class Comp::T;
    static void sort(Vector <Comp::T> &);
    };

    What is the reason for declaring class Comp: :T;? If the purpose is to make static void sort() possible to declare with parameter type involving Comp::T, sure, Comp and Comp::T must have defined (declared) before the definition of Sort<>.

    Comp is a type which has a member T which is also a type. This is to be replaced with "typedef Comp::T" when compilers become ISO compliant.

  17. Why can't we use virtual and _fastcall function modifiers together in Borland C++ 3.1?

    '_fastcall' modifies the method of calling a function (or member function) in a manner similar to the '_pascal specifier. With '_fastcall' functions the compiler attempts to put all the parameters in registers (a maximum of three parameters may be passed). Reasons for incompatibility may be that the virtual member function call probably requires the use of more registers than a normal call thus making '_fastcall' less useful or that the mechanism for passing the parameters to all members of the polymorphic hierarchy must be the same, this might be difficult to ensure (i.e. all polymorphic member functions would have to be '_fastcall' types. By the way, its non-portable and changes to '__fastcall' (2 underscores) in BC 4.0!

  18. class A { 
    public:
    virtual void f();
    };
    class B: public A {
    public:
    virtual void f(int);
    };

    Why does Borland C++ 3.1 warns A::f() is being hidden by B::f(int)?

    Because it is being hidden. You cannot call A::f() for a B object as the overload set of B is not a superset of A for the function f

  19. When a function with default is called indirectly through function pointer, e.g.

    void f(int a = 3);
    void (*pf)() = f;  // is this allowed?
    (*pf)();  (*pf)(l); // how about these 2 calls?

    The type of f is a function taking int and returning void. The type of pf is a pointer to a function taking no arguments and returning void. The initialisation of pf is not allowed because they are different types. A default argument affects how a function is called and not its type.

    Of the last 2, the first call is undefined, the second is ill-formed.

  20. In class  D below, are both the constructors for class A or class  B called?

    Is the object d not stable?

    class A{
    :
    :
    A();
    };
    class B{
    :
    :
    B();
    }
    union C {
    A a;
    b b;
    };
    class D {
    C c;
    :
    :
    } d;

    An object of a class with a constructor or destructor or a user-defined operator cannot be a member of a union. So sayeth P182 of the ARM (1st sentence). Your BC 3.1 compiler should have given you the error "Union member C::a is of type class with constructor".

PolonTang

Hi Mike,

I'm still reading through Overload 3 - good stuff! -- and I just saw the comments about getting someone to write an ANSI-compliant string class. Well, of course, Borland have! In fact, Pete Becker even disappeared one day during the meeting — everyone assumed to implement the subscript operators proposed by John Max Skaller (ISO Australia) and adopted by the full committee. Also note that, since the article was written, the class Size_T has been dropped (plain size_t to be used instead), and a resize method has been added to match the dynarray<T> template library class. I believe some minor changes were made to the constructors too, but I don't have the info to hand (sorry!).

Regards, Sean Corfield

Thanks for the info Sean. Not everyone will have access to a compiler that has an ANSI-compliant string class. I hope that it can be implemented so that it can be used in conjunction with compilers that do not support exceptions.

Since we last communicated I have received and read the offering of "Overload". I may say that I was pleasantly surprised with this since there were several articles that were of interest to me. Maybe it's because I have many years' C experience but am still learning the vastly more complicated ways of C++.

I was particularly interested in article in Cheshire cats. It so happens that I was contemplating the use of pointer classes for another reason - automatic garbage collection. The problem with memory management in C is that you can define pointers all over the place and so no bit of data knows when nothing is depending on it, so the programmer has to delete chunks of data explicitly when he things they are no longer required. We all know that this is no always easy to work out. C++ ought to do better, but it still allows pointers and so fails miserably. Pointers are the problem, so if we ban them completely from "normal" code, then there should be a method to keep track of data use by the age-old method of reference counting:

class Ref 
{
public:
void ref() { if (this) count++; };
void deref()
{
if (this && --count == 0)
delete this;
};
protected:
Ref() { count = 0; };
virtual ~Ref(){};
private:
int count;
};

class Ref_struct

class Ref_ptr
{
public:
Ref_ptr(Ref_struct *p=0);
Ref_ptr(Ref_ptr  &r) ;
~Ref_ptr();
Ref_ptr& operator =(Ref_ptr &r)
{
point_at(r.ptr);
return *this;
};
void New();
Ref_struct *operator ->()
{
return ptr;
};
protected:
void point_at(Ref_struct *p);
Ref_struct *ptr;
};

class Ref_struct : public Ref
{
// The only thing that can create or delete
friend void Ref_ptr::New();
Ref_struct();
~Ref_struct();
public:
// The public interface
protected:
};

Ref_ptr::Ref_ptr(Ref_struct *p)
: ptr(p)
{
p->ref();
}

Ref_ptr::Ref_ptr(Ref__ptr &r)
: ptr(r.ptr)
{
ptr->ref();
}

Ref_ptr::~Ref_ptr()
{
ptr->deref();
}

void Ref_ptr::point_at(Ref_struct *p)
{
// Must refer to the object in case
//the deref accidently kills it
p->ref();
ptr->deref();
ptr = p;
}

void Ref_ptr::New()
{
ptr = new Ref_struct();
ptr->ref();
}

The public interface of "Ref_struct" is available via the pointer operator on a Ref_ptr, so to the user it looks just like a "real" pointer. Pointer types need to be created for each structure type (so that New() works). The pointer types could all be derived directly from "Ref_ptr" or follow the hierarchy of the structures - it rather depends on whether you want to add any additional functions to the pointer types that might be usefully inherited.

One such use of additional functions is overloading of operators - standard pointers have non-overloadable operations such as "+". With these defined pointer types "+" (or any other operator) could be overloaded to be something more useful.

I don't think that these ideas are anything new, indeed Stroustrup refers in passing to "smart pointers" that might update reference counts. Nor do I guarantee that the above code it perfect - I ran a few small tests and it seems to be OK.

One suggestion for layout of "Overload" - can you arrange for code snippets to have the right number of characters in each line for it to fit in a column? I think this is 59 or 60 chars. It is very confusing having // comments wrapping round to the next line.

Thanks, Colin.

In future I will take extra care with wrapping comment lines. As you can see from your original text, the comment lines were far too long for the column space available. I hope this solution is to your taste. - Mike.

Overload Journal #4 - Feb 1994 + Letters to the Editor