Archived

This forum has been archived. Please start a new discussion on GitHub.

Documentation Clarification, Perhaps

Hi,

Here are a couple of suggestions that I think would make it easier for new users to understand ICE.

1) Document all the useful classes in a "class reference". Some of this is already in "Slice Documentation", but it would be great if non-slice classes (and language-specific issues) had similar documentation. The chapter title also isn't what a newcomer would normally look for.

2) The slice documentation (chapter 4) talks about classes being passed by value (class types) and reference (proxy types). To me, this is very misleading -- especially in C++, since classes are implemented as smart pointers, which are in essence, references to objects. The C++ documentation doesn't do a great job about explaining this mechanism either. I think that when discussed in these forums, it has been made clear that structs are "value types" and classes are "reference types", but this doesn't mesh with the way things are described in 4.11.5 and 4.11.11. If there is a good discussion of the generated Ptr types, I haven't seen it. I'd be happy to write something if that would be helpful.

Cheers,

Comments

  • Hi Andrew,

    thanks for pointing this out. I'll have a look at what we can do to improve this.

    Cheers,

    Michi.
  • Hi Andrew,

    you can find a detailed discussion of Ptr types in the client-side C++ mapping chapter.

    Structs are value types, as you would expect. Classes are value types, but operations on classes can be accessed via a proxy, that is, classes can act as servants. Here is a simple class:
    class X { int i; };
    
    That class behaves just like a struct, that is, it is passed by value. A more complex example:
    class Base { int i; };
    class Derived extends X { int j; };
    
    That's one difference between classes and structs: classes support inheritance.

    If you pass an instance of this class, the most-derived type of the class that is understood by the receiver is what the receiver gets. For example:
    interface Example {
        void foo(Base b);
    };
    
    At run time, you can pass a Base instance to foo(). If you do, the receiver (obviously) receives that Base instance. On the other hand, at run time, you can pass a Derived instance to foo(). If you do, the receiver will receive the derived instance provided that the receiver has knowledge of the type Derived. On the other hand, if the receiver doesn't have knowledge of the type Derived (for example, because type Derived was added to the type system after the receiver was compiled), the Derived instance is sliced to type Base, and the receiver gets a Base.

    For classes that point at other classes (that is, classes with class members), you can create graphs. If you pass one node of such a graph to an operation, all nodes that are reachable from that starting node are marshaled to the receiver. In other words, the entire graph of class instances is rebuilt in the receiver.

    So, you can think of classes as structs on steroids: they support inheritance, slicing to a base type if the receiver doesn't have knowledge of the actual most-derived type, and they support pointer semantics. (Structures don't do any of these things.)

    For classes with operations, their value semantics do not change. Classes are still passed by value, that is, their data members are marshaled. Invoking an operation on a class via its Ptr always invokes the operation on the copy of the class in the local address space. Ergo, the code that implements the operations on a class must be linked into the local address space for that to work. (This is also the reason why you have to register a class factory for classes with operations--without the factory, the Ice run time would have no idea which programming-language class to instantiate because that class must implement the operations.)

    You can also use a class as the implementation of an interface:
    interface Base {
        void foo();
    };
    
    class Derived implements Base {
    {
       int i;
       void bar();
    };
    

    Now if you pass an instance of this class around, it will still be passed by value, that is, the value of the data member i will be sent to the other end, and calling bar() via a class's Ptr calls the bar() in the local address space. Similarly, you can invoke the base operation foo() on the class via its Ptr. If you do, you will also call the foo() in the local address space.

    However, if a class implements an interface, you can also create a proxy to that class. In that case, you have created a proxy to an instance of the class, but the class can live in a remote address space. In other words, if you create a proxy to a class that implements an interface, you have effectively created a proxy to an Ice object that implements the interface supported by the class.

    For the above example, if you create a BasePrx, you can call foo() via that proxy. The invocation will go to whatever address space is denoted by the proxy. Similarly, you can create a DerviedPrx. Via the DerivedPrx, you can call both foo() and bar(), and either invocation will go to the address space denoted by the proxy.

    So, in summary, invocations via a Ptr are always local, and invocations via Prx go to the address space denoted by the proxy (which may be local but, typically, is remote).

    Finally, you can declare an operation like this:
    interface Base { /* ... */ };
    
    interface Example {
        void op(Base b);
    };
    
    Note that op() accepts an interface by value.

    Of course, interfaces cannot be passed by value. However, classes that implement the interfaces can. In other words, op() is an operation that accepts a class that implements Base and sends that class instance by value to the other end. Of course, the receiver will get a BasePtr from the Ice run time and, to access the actual derived part, will have to do a type-safe down-cast on the Ptr, for example, by calling DerivedPtr::dynamicCast(b).

    Hope this helps!

    Cheers,

    Michi.
  • Class Ptr Types from slice

    Hi,

    Thanks for the explanation. I think that there are two disconnects here.

    1) I can't find anywhere in the documentation where it says that a class type in an operation will map to a class Ptr type in C++. This has taken several of my people a while to get square with:

    slice:

    class foo
    {
    int a;
    };

    foo op(foo) -> slice2cpp -> fooPtr op(fooPtr, Ice::Current) [ for server ]

    The documentation says "Slice classes are mapped to C++ classes with the same name." This is true, but NOT true when their context is an operation. In such cases, slice classes are mapped to references (Ptrs) to C++ classes of the same name.

    2) When you use the term reference, you are thinking in terms of an ICE object. When my people are using the term reference with regard to C++ code, they are thinking of passing things by reference in C++ or as an indirection to an object that exists IN THEIR ADDRESS SPACE, not over the wire. Its just the terminology that gets confusing.

    When you get a fooPtr, you have a REFERENCE to a foo object that exists in the local address space. When you say that classes are passed by value, you mean that a copy of the class object is sent across the wire, but in the C++ mapping, when someone get a class object (as defined in slice), they get a reference to the object (a fooPtr) that exists in the local address space.

    I have had repeated conversations with people that work with me on this. It keeps coming up. This is why I think some terminology clarification in the documentation with regard to the class slice -> C++ mapping for operations would be helpful.

    Thanks,
  • marc
    marc Florida
    In general, we try to avoid the term "reference", since it has way too many meanings. Instead, we use the following:
    • "Proxy" for "references to an Ice object".
    • "Smart pointers" for "references to a C++ object".
    • "C++ reference" to refer to the C++ by-reference passing mechanism
    • "Java reference" to refer to the Java references.
    Michi didn't use the term "reference" in his post above. If we use the term "reference" without further qualification somewhere in our documentation, please let me know where, and we will fix it.