Re: STL problem in stored procedures

From: Andreas Seltenreich <andreas+pg(at)gate450(dot)dyndns(dot)org>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: Kevin Murphy <murphy(at)genome(dot)chop(dot)edu>, PostgreSQL general <pgsql-general(at)postgresql(dot)org>
Subject: Re: STL problem in stored procedures
Date: 2005-10-25 17:02:02
Message-ID: 87acgxjzsl.fsf@gate450.dyndns.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-general

Tom Lane schrob:

> Kevin Murphy <murphy(at)genome(dot)chop(dot)edu> writes:
>> Tom Lane wrote:
>>> I think he needs to rewrite in C :-(. The backend is not C++ and I
>>> fear it's unlikely that libc++ will play nicely as a dynamic add-on.
>
>> It would be great if some C++/C guru could make a thorough analysis of
>> C++ integration issues.
>
> The main thing that scares me is lack of integration of C error handling
> (setjmp/longjmp) with C++ error handling (catch/throw). In a
> self-contained module you could perhaps avoid this by not using
> catch/throw, but I don't think you get to ignore the issue if you are
> using STL modules.
>
> It *might* work to put a generic "catch/report via elog" handler around
> each one of your entry-point functions. Haven't tried it.

Hmm, this setup worked quite stable here for some smaller educational
projects. The snippet I used to wrap the C++ was:

--8<---------------cut here---------------start------------->8---
#include <stdexcept>

extern "C" {
#include <postgres.h>
#include <fmgr.h>
}

extern "C" {
PG_FUNCTION_INFO_V1(cpptest);
Datum cpptest(PG_FUNCTION_ARGS);
}

Datum
cpptest(PG_FUNCTION_ARGS)
{
try {

throw std::runtime_error("Your code here.");

}
catch (std::exception &e) {
elog(ERROR, "caught C++ exception: %s", e.what());
}
catch (...) {
elog(ERROR, "caught non-standard C++ exception.");
}
}
--8<---------------cut here---------------end--------------->8---

I was even able to spare the exception code and the extern "C"s in
each and every function by hacking together a language handler loading
functors (classes overloading the operator "()") using the class
loader documented in this paper:
<http://www.s11n.net/papers/classloading_cpp.html> (this one is
exceptional in that it contains no demangling code for symbol lookup
and doesn't need any non-portable libraries).

> This all assumes that libc++ can cooperate with, rather than try to
> replace, libc in the first place. That would depend a lot on what
> platform you are on --- I think it might work OK with modern glibc,
> but my last experience with C++ was back when it wasn't so.

The most annoying non-cooperation for me was the STL's ignorance of
Pg's MemoryContexts. I managed to tame it with a wrapper around palloc
with an STL allocator interface:

--8<---------------cut here---------------start------------->8---
// $Id: pg_alloc.hh,v 1.2 2005/10/19 09:59:13 andreas Exp $
//
// This implements an allocator template with STL interface as a
// wrapper around PostgreSQL's MemoryContexts.
//
// It enables STL containers to use appropriate MemoryContexts instead
// of C++'s "free store".

#include <stdexcept>

extern "C" {
#include <postgres.h>
}

template <class T, MemoryContext *M = &CurrentMemoryContext>
class pg_allocator
{
public:
typedef T value_type;
typedef size_t size_type;
typedef ptrdiff_t difference_type;

typedef T* pointer;
typedef const T* const_pointer;

typedef T& reference;
typedef const T& const_reference;

pointer address(reference r) { return &r; }
const_pointer address(const_reference r) { return &r; }

pg_allocator() throw() { };
template <class U> pg_allocator(const pg_allocator<U, M>&) throw() { };
~pg_allocator() throw() { };

pointer allocate(size_type n, const T *hint = 0) throw(std::bad_alloc);
void deallocate(pointer p, size_type n);

void construct(pointer p, const T& val) { new(p) T(val); }
void destroy(pointer p) { p->~T(); }

size_type max_size() const throw();

template <class U>
struct rebind {typedef pg_allocator<U, M> other; };
};

template <class T, MemoryContext *M>
T* pg_allocator<T, M>::allocate(size_type n, const T * hint) throw(std::bad_alloc)
{
void* ptr = 0;

ptr = MemoryContextAlloc(*M, (n * sizeof(T)));

if (ptr)
return static_cast<T*>(ptr);

throw std::bad_alloc();
}

template <class T, MemoryContext *M>
void pg_allocator<T, M>::deallocate(T* ptr, size_t n)
{
pfree(static_cast<void *>(ptr));
}

template <class T, MemoryContext *M>
bool operator==(const pg_allocator<T, M>& a, const pg_allocator<T, M>& b) {
return true;
}

template <class T, MemoryContext *M>
bool operator!=(const pg_allocator<T, M>& a, const pg_allocator<T, M>& b) {
return false;
}
--8<---------------cut here---------------end--------------->8---

E.g., to allocate a list of strings on the CurrentMemoryContext:
list<string, pg_allocator<string>> foo;
You can supply a custom context with the second template argument if
you need, e.g., a session-wide cache of something.

At this level, I find C++ quite usable as a compromise between C and
the comparatively slow procedural languages. Of course, the interface
to PostgreSQL is still all PODs (plain old datatypes), but C++'s C
compatibility should be seen as a strength here.

regards,
Andreas
--

In response to

Responses

Browse pgsql-general by date

  From Date Subject
Next Message Richard Huxton 2005-10-25 17:11:49 Re: Deleting vs foreign keys
Previous Message Steve Atkins 2005-10-25 16:54:00 Re: Select all invalid e-mail addresses