EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

2002  2003  2004  <20052006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  Index 2002  2003  2004  <20052006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: Standard String
From: Kay-Uwe Kasemir <[email protected]>
To: EPICS Core Talk <[email protected]>
Date: Tue, 19 Jul 2005 11:43:50 -0400
Hi:

I think this is quite usable. Comments:

There obviously need to be many more comments or a manual section
on how the EpicsBufferCreator works.

'size', 'maxsize' and 'capacity' are sort of understood
from the STL, but I'd still prefer a simple 'length' for the
string length.

I also maintain that in the real world
interfacing with (char *) is an extremely important
"use case" for the string class.

1. Creation from char *
      EpicsString(const char *literal);
      EpicsString(const char *bufferType, epicsInt32 capacity);
are good for initializing an EpicsString from char * data.
The MFC CString and the Cocoa::NSString are two examples of
string classes that also support an sprintf-type routine
for initializing or changing the string, so how about:
      format(const char *printf_format, ...)
?

2. Comparison with char *
Does
  isEqual(epicsInt32 offset, epicsInt32 len, const epicsOctet *pdata)
work because epicsOctet converts to char?
Otherwise I propose to add
  bool isEqual(epicsInt32 offset, epicsInt32 len, const char *c_str).

3. Obtaining a char * from the String
MFC::CString, std::string, Cocoa::NSString all include
a c_str()-type routine that gives you a (char *) string.

We can argue all we want how evil that is,
we still have to provide something very similar.

Something that copies into a char * handles the non-contiguous
strings like this would work:
    // Copy into user-provided buffer.
    // Initialize len with sizeof(buf).
    // expose() updates len to # of characters actually copied.
    // buffer will always be terminated with '\0',
    // so you get max. len-1 as a string length.
    bool expose(epicsInt32 offset, epicsInt32 &len, char *buffer);

But even with that, it would still be much more convenient
to end users to provide this:

	// Returns copy of String as C-type string.
    // Caller has to delete!
	const char *EpicsStringAccess::getCstring()

4. Up/down
Other string classes often include 'toupper', 'tolower',
and a case-insensitive comparison.
Should we include at least the latter?

Thanks,
-Kay


On Jul 19, 2005, at 08:03, Marty Kraimer wrote:

What about the following:

class EpicsStringAccess {
public:
   virtual void createBuffer(
                const char *bufferType, epicsInt32 capacity = 0) = 0;
   virtual void createBuffer(
EpicsBufferCreator *creator, epicsInt32 capacity = 0) = 0;
   virtual EpicsBufferCreator *bufferCreator() const = 0;
   virtual void destroyBuffer();
   virtual epicsInt32 get(epicsInt32 offset, epicsInt32 len,
                  epicsOctet *pto) const = 0;
   virtual epicsInt32 put(epicsInt32 offset, epicsInt32 len,
                  const epicsOctet *pfrom) = 0;
   // These routines are as described for EpicsBuffer
   virtual void reserve(epicsInt32 capacity) = 0;
   virtual epicsInt32 capacity() const = 0;
   virtual void resize(epicsInt32 newsize) = 0;
   virtual epicsInt32 size() const = 0;
   virtual epicsInt32 maxSize() const = 0;
   virtual bool mutable() const = 0;
   virtual bool isEqual(const EpicsBuffer &cmp) const = 0;
   virtual bool isEqual(epicsInt32 offset, epicsInt32 len,
                const epicsOctet *pdata) const = 0;
   virtual bool expose(epicsInt32 offset, epicsInt32 &len,
               epicsOctet *&pdata) = 0;
   virtual bool expose(epicsInt32 offset, epicsInt32 &len,
               const epicsOctet *&pdata) const = 0;
};

class EpicsString : public EpicsStringAccess {
public:
   EpicsString();
   EpicsString(const char *literal);
   EpicsString(const char *bufferType, epicsInt32 capacity);
   EpicsString(EpicsBufferCreator *creator, epicsInt32 capacity);
   virtual ~EpicsString();
   EpicsString& operator=(const EpicsString &rhs);
   void createBuffer(const char *bufferType, epicsInt32 capacity = 0)
void createBuffer(EpicsBufferCreator *creator, epicsInt32 capacity = 0);
   EpicsBufferCreator *bufferCreator() const;
   void destroyBuffer();
   epicsInt32 get(epicsInt32 offset, epicsInt32 len,
                  epicsOctet *pto) const;
   epicsInt32 put(epicsInt32 offset, epicsInt32 len,
                  const epicsOctet *pfrom);
   // These routines are as described for EpicsBuffer
   void reserve(epicsInt32 capacity);
   epicsInt32 capacity() const;
   void resize(epicsInt32 newsize);
   epicsInt32 size() const;
   epicsInt32 maxSize() const;
   bool mutable() const;
   bool isEqual(const EpicsBuffer &cmp) const;
   bool isEqual(epicsInt32 offset, epicsInt32 len,
                const epicsOctet *pdata) const;
   bool expose(epicsInt32 offset, epicsInt32 &len,
               epicsOctet *&pdata);
   bool expose(epicsInt32 offset, epicsInt32 &len,
               const epicsOctet *&pdata) const;
private:
   EpicsBuffer *pbuffer;
private:   // Prevent compiler-implemented methods
   EpicsString(const EpicsString &str);   // No copy constructor
};

epicsBoolean operator==(const EpicsString &lhs, const EpicsString &rhs); epicsBoolean operator!=(const EpicsString &lhs, const EpicsString &rhs);


EpicsStringAccess is the interface. EpicsString is what Andrew wants.

Marty Kraimer

Jeff Hill wrote:

but I will not accept a standard string class which is just a pure virtual interface. I hope the above discussion shows why.


1) You are saying that if the implementation is created at runtime, and its
not on the stack then whoever called the factory to create it has to
remember to return it to the recycler when they are done using it, and its nice to have a helper class so this is done automatically. That's all fine, tasty, American apple pie, but my perception is that this argument about lifetime management is completely orthogonal to discussions about interfaces
for manipulating the object.

2) Sure, classes that remember to destroy the object when we are done using it are nice, and I definitely do use them. However, one has to put this in perspective. No matter how many times you wrap it with auto destroying this and that you still have the same responsibility. Users will sometimes want to have a pointer to it, and therefore whoever called the factory to create it has to remember to return it to the recycler when they are done using it.
Most "users" don't have a problem with that.

3) There is some extended discussion about assignment to the string, but since assignment is possible through a pure virtual interface then I am
having problems determine the relevance of that thread of discussion.

4) You should certainly have freedom to use what works best to store a
string in a field in a record, and if users and databases have similar
requirements then perhaps the same implementation of a string should be used
by them also (by-the-users is a pretty general term). However, optimal
design will be to use a common pure virtual interface and allow many
different implementations. For example, if the string storage is a C "string constant" then a very simple, and fast, implementation is better. With CA the storage needs to be CA protocol buffers. I already have a library for creating, destroying, and manipulating protocol buffers. My hope is that a pure virtual string interface can be used to directly manipulate strings
residing in protocol buffers.

5) The important distinction is that storage management and implementation
can be and *should* be application specific (in this context database
storage specific). In contrast, important interfaces like dbPutField,
dbGetField, dbGetLink, and anything else that manipulates strings should use an pure virtual interface so that A) these codes do not break if the string
implementation is changed, and B) codes like CA (and CA interfaced
applications) are free to manage string storage and implementation in a way
that is optimal for their situation.

6) Lastly (and of less significance), sorry to dredge up the past, but I can't help but have a very clear memory of lashings I received at design meetings for not following appropriate design methodology. During EPICS R3.14 design the concerted opinion of the APS contingent was that the design of epicsTimer was unacceptable unless it was changed to be pure virtual. To
be specific, the APS mandated that the design be changed so that an
epicsTimer must be created by calling a factory method returning a reference to a pure virtual interface. I can't say that I didn't benefit from the
experience :-)

Jeff


-----Original Message-----
From: Andrew Johnson [mailto:[email protected]]

Jeff said that he wanted it to be an interface class.

Let's look at that idea, without worrying about what it actually looks like: Assume we have all agreed on a single API for a string class that
I'll call 'StringInterface,' which is a pure interface class.

I can't instantiate an object of type StringInterface since it's a pure
interface class, so in order to create and use a string in my code I
would have to define a pointer to a StringInterface, and call a factory routine or use some other means to create a concrete object of my chosen
derived class.

    StringInterface *pstring = StringFactory(...);

However because I'm using a pointer, I must remember to release that
object (assume for now that just means deleting it) after I've finished
with it in order to avoid leaking memory when my pstring goes out of

scope:

    delete pstring;

Since I'm a well-educated C++ programmer who understands the RAII idiom
(Resource Acquisition Is Initialization), I know how to solve the
potential memory leak issue: wrap the pointer inside another class which
has a destructor that will delete the string for me.

I could use the standard C++ auto_ptr template to wrap and manage a
StringInterface pointer, but I don't want the destructive assignment
behaviour that it would give me.

I decide to create a new class implementing StringInterface that
contains and manages a StringInterface pointer for me. It will release
the object automatically in its destructor, so no memory leaks.  That
class will look something like this:

class SafeString public StringInterface {
private: // Data
    StringInterface *psi;
public: // Methods
    SafeString(StringInterface *concrete = 0) :
        psi(concrete) {}
    ~SafeString()
    {
        if (psi) delete psi;
    }
    SetStringInterface(StringInterface *concrete)
    {
        if (psi) delete psi;
        psi = concrete;
    }
    // For every method in StringInterface, define
    returnType method(...)
    {
        if (!psi) throw(something...);
        psi->method(...);
    }
private: // Not implemented:
    SafeString(const SafeString& rhs);
    SafeString& operator=(const SafeString& rhs);
};

I can now create an instance of a SafeString and know that I can't leak
memory by forgetting to delete the pointer:

    SafeString greeting(StringFactory(...));

SafeString isn't a pure interface class since it contains data, but the only behaviour it actually defines for itself is the ability to manage that pointer for me. I haven't sacrificed any flexibilty on the part of the underlying implementation of StringInterface since I forward all the StringInterface methods to the real implementation, I have just made it
safer to use the StringInterface API.

I'd like to make my SafeString slightly easier to use in a couple of
very common cases:

1. Suppose there's an implementation of StringInterface available
through the StringFactory that takes a C++ 'const char *' literal and
makes it accessible using the StringInterface methods. I decide to add
a constructor to my SafeString class that explicitly creates one of
those kinds of strings when given a 'const char *':

    SafeString(const char *pstr) :
        psi(StringFactory(..., pstr)) {}

I can now write code like this:

    SafeString greeting("Hello, world!");

which is the same as writing this:

    SafeString greeting = "Hello, world!";


2. I can't assign anything to a SafeString at the moment.  I declared
the self-assignment operator private in the class definition above in
order to prevent the compiler from creating one for me that would have just copied the psi pointer, which is definitely the wrong thing to do. Assuming that StringInterface can manipulate a mutable string it must provide some way to copy one string to another. I'll change my class to
add an assigment operator using that method:

    SafeString& operator=(const StringInterface& rhs)
    {
        if (!psi) throw(something...);
        // Assuming StringInterface::assign(const StringInterface &)
        psi->assign(rhs);
    }

This now means I can write this:

    SafeString greeting = "Hello";
    SafeString reply = StringFactory(...); // something mutable
    reply = greeting;

Note that there's no copy constructor for SafeString, so I definitely
can't write this:

   SafeString greeting = "Hello";
   SafeString reply = greeting; // Link error, not implemented

The copy constructor for reply would have no way to tell what kind of
string I want it to instantiate. If StringInterface provided a clone() method I could implement a copy constructor to clone the StringInterface
object that it's copying, but if it doesn't then the above code will
always have to give a link error.  For the purposes of the current
argument I don't really care about that, but it's something to bear in
mind.


Ok, so I've now defined a class that implements and uses our agreed
StringInterface API.  It's exactly the same size as a StringInterface
pointer, but my class is safe from memory leaks and is more friendly to
users.  Which should we encourage EPICS V4 users to develop with -
StringInterface pointers, or my SafeString class?

Now please compare SafeString to the overall shape of the definition of
EpicsString as found in the Wiki page "V4 Design: epicsTypes" at
http://www.aps.anl.gov/epics/wiki/index.php/V4_Design: _epicsTypes#epicsStr
ing


I would be happy to discuss changes to (or even a complete replacement
for) the EpicsBuffer interface which EpicsString was based on, and
changes to the methods of EpicsString itself. We can talk about whether
an EpicsString IS an EpicsBuffer, HAS an EpicsBuffer or even
IS-IMPLEMENTED-IN-TERMS-OF an EpicsBuffer (EffC++), but I will not
accept a standard string class which is just a pure virtual interface.
I hope the above discussion shows why.


Jeff, I'll be commenting on your reply in a separate message.

- Andrew

PS: Why aren't we discussing this on core-talk?
--
Podiabombastic: The tendency to shoot oneself in the foot.








References:
RE: Standard String Jeff Hill
Re: Standard String Marty Kraimer

Navigate by Date:
Prev: FW: Standard String Jeff Hill
Next: Re: FW: Standard String Benjamin Franksen
Index: 2002  2003  2004  <20052006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: Standard String Marty Kraimer
Next: FW: Standard String Jeff Hill
Index: 2002  2003  2004  <20052006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
ANJ, 02 Feb 2012 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·