c++ using declaration, scope and access control
Asked Answered
P

3

12

Typically the 'using' declaration is used to bring into scope some member functions of base classes that would otherwise be hidden. From that point of view it is only a mechanism for making accessible information more convenient to use.
However: the 'using' declaration can also be used to change access constraints (not only for functions but also for attributes). For example:

class C{
public:
  int a;
  void g(){ cout << "C:g()\n"; }
  C() : a(0){}
};

class D : public C{
private:
  using C::a;
  using C::g;
public:
  D() { a = 1; }
};

int main(void){
  D d;
  cout << d.a << endl;  //error: a is inaccessible
  C *cp = &d;
  cout << cp->a << endl; //works
  d.g();  //error: g is inaccessible
  cp->g();  //works
  return 0;
}

I think this limitation of access in the derived class is actually of no use, because you can always access g() and a from a pointer to the base class. So should't there be at least some kind of compiler warning? Or wouldn't it been even better to forbid such limitation of access by a derived class? The using declaration is not the only possibility to add constraints to access. It could also be done via overriding a base class' function an placing it in a section with more access constraints. Are there some reasonable examples where it is indeed nessecary to limit access in such a way? If not I don't see why it should be allowed.

And another thing: at least with g++ the same code compiles well without the word 'using'. That means for the example above: it's possible to write C::a; and C::g; instead of using C::a; using C::g; Is the first only a shortcut for the latter or are there some subtle differences?

//EDIT:
so from the discussion and answers below my conclusion would be:
- it's allowed to limit access constraints in derived classes with public inheritance
- there are useful examples where it could be used
- it's use might cause problem in combination with templates (e.g. a derived class could not be a valid parameter for some template class/function any more although it's base is)
- a cleaner language design should not allow such use
- compiler could at least issue some kind of warning

Procurable answered 18/1, 2010 at 8:54 Comment(0)
T
6

With regard to your declaration without using: These are called "access declarations", and are deprecated. Here is the text from the Standard, from 11.3/1:

The access of a member of a base class can be changed in the derived class by mentioning its qualified-id in the derived class declaration. Such mention is called an access declaration. The effect of an access declaration qualified-id; is defined to be equivalent to the declaration usingqualified-id; [Footnote: Access declarations are deprecated; member using-declarations (7.3.3) provide a better means of doing the same things. In earlier versions of the C++ language, access declarations were more limited; they were generalized and made equivalent to using-declarations - end footnote]

I would say that most often it's not good to change public members to private or protected members in the derived class, because this will violate the substitution principle: You know a base class has some functions, and if you cast to a derived class then you expect those functions to be callable too, because the derived class is-a base. And like you already mentioned, this invariant is already enforced anyway by the language allowing to convert (which working implicitly!) to a base class reference, or qualifying the function name, and then calling the (then public) function.

If you want to forbid someone calling a set of functions of the base, then i think this hints that containment (or in rare cases, private inheritance) is a better idea.

Trierarch answered 18/1, 2010 at 9:30 Comment(5)
Not to pick on the answer, but rather to bring some discussion into it: Is the substitution principle really violated? In the dynamic version of polymorphism it is not. Code that works on base references or pointers will also work when passed in a derived object, as it will be implicitly upcasted to base. That is different once you add templates and static polymorphism to the mix. There it is true that a template designed to work on a base argument will not work on a derived object. But the whole purpose of limiting access could be that (this is a base that cannot be used there)?Beaufert
Substitution principle means, that whenever you expect an Object of type base (e.g. pointer, argument passing etc.) you can also pass an Object of a "better" type - and better types in C++ are derived classes with public base. With non-public inheritance the Substitution is not allowed any more, because of the access constraints. So I think changing public members to private does not violate the substitution principle in public inheritance, because you simply don't really restrict the access constraints (always available from base). @dribeas: could you explain the template thing a bit more?Procurable
"whenever you expect an object of type base, you can also pass an object of a better type" - but this is violated here. Imagine your iterator inherits std::iterator, and then does private: using iterator::value_type; - it fail with standard algorithms: Your class will not be able to substitute an iterator anymore, because there is no public value_type member. As dribeas said it does not violate when you merely check dynamic polymorphism. But when templates are in place, this becomes observable.Trierarch
ok, you're right with that point - but is that still substitution? When dealing with templates an concrete instance of that template is generated at compile time and there is no global function taking an argument of some base class any more. I think the standard algorithms are also templates? And if you try to use some of these algorithms with your class this should fail at compile time.Procurable
Access is only checked at compile time, so if there is a problem it will be at compile time. About whether it is substitution... I do think it is, just different. The templated algorithms are not different to the equivalent algorithm in a dynamic language (think python): the real requirement is that the interface provides. If you read the standard (and until concepts get a second chance to get in there) the template arguments to STL algorithms have names with standard defined interfaces: ForwardIterator, RandomAccessIterator, so all iterators are somehow derived from those.Beaufert
S
3

While the using declaration you showed does provide a mechanism to change access level (but only down), that is not the primary use of it in such a context. A using context there is primarily intended to allow access to functions that would otherwise be shadowed from the base class due to the language mechanics. E.g.

class A {
public:
   void A();
   void B();
};

class B {
public:
   using A::B;
   void B(int); //This would shadow A::B if not for a using declaration
};
Streak answered 18/1, 2010 at 9:1 Comment(1)
I think he already knows that: "Typically the 'using' declaration is used to bring into scope some member functions of base classes that would otherwise be hidden.". To me it sounds like he is specifically asking about the access level issue.Trierarch
B
0

The declaration

using C::a

brings "a" to the local naming scope so that you can later use "a" to refere to "C::a"; since that, "C::a" and "a" are interchangeable as long as you don't declare a local variable with name "a".

The declaration does not change access rights; you can access "a" in the subclass only because "a" is not private.

Blagoveshchensk answered 18/1, 2010 at 8:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.